commiting while gfx ar enot working right

This commit is contained in:
Martin Donnelly 2020-05-10 11:38:13 +01:00
parent 7d74fcc3e0
commit 29c7c7e132
48 changed files with 2400 additions and 129 deletions

4
copy.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
# rm -rf /home/martin/dev/menuserver/dist/*
cp -r /home/martin/dev/svelte-traintimes/public/* /home/martin/dev/traintimesPWA/live

26
package-lock.json generated
View File

@ -1640,6 +1640,11 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true
},
"immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
},
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
@ -2221,6 +2226,14 @@
"type-check": "~0.3.2"
}
},
"lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
"requires": {
"immediate": "~3.0.5"
}
},
"livereload": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.1.tgz",
@ -2257,6 +2270,14 @@
"resolved": "https://registry.npmjs.org/local-access/-/local-access-1.0.1.tgz",
"integrity": "sha512-ykt2pgN0aqIy6KQC1CqdWTWkmUwNgaOS6dcpHVjyBJONA+Xi7AtSB1vuxC/U/0tjIP3wcRudwQk1YYzUvzk2bA=="
},
"localforage": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz",
"integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==",
"requires": {
"lie": "3.1.1"
}
},
"locate-path": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
@ -3119,6 +3140,11 @@
"picomatch": "^2.0.7"
}
},
"redaxios": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/redaxios/-/redaxios-0.2.0.tgz",
"integrity": "sha512-lrDld2bVWIBrW+S1HPMyP8OvEtpHPIfmXyW7T7xTox1SM4/M3aC5X55xa2vRR3LZHwCvpMQAgCye8IyICzkKpA=="
},
"redent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",

View File

@ -24,7 +24,9 @@
},
"dependencies": {
"axios": "^0.19.2",
"localforage": "^1.7.3",
"muicss": "^0.10.1",
"redaxios": "^0.2.0",
"rollup-plugin-replace": "^2.2.0",
"sirv-cli": "^0.4.4",
"spectre.css": "^0.5.8",

9
public/browserconfig.xml Normal file
View 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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 49 KiB

BIN
public/img/Icon-144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
public/img/Icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/img/Icon-36.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/img/Icon-48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
public/img/Icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
public/img/Icon-72.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/img/Icon-96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/img/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
public/img/photothumb.db Normal file

Binary file not shown.

View 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

View File

@ -4,10 +4,22 @@
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<title>Traintimes</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<!--<link rel='stylesheet' href='/global.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">
<!--<link rel='stylesheet' href='/global.c
ss'>-->
<link rel='stylesheet' href='/build/bundle.css'>
<script defer src='/build/bundle.js'></script>

46
public/manifest.json Normal file
View File

@ -0,0 +1,46 @@
{
"name": "Train Times",
"short_name": "Train Times",
"icons": [
{
"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",
"background_color": "#ffffff",
"start_url": ".",
"imgdisplay": "standalone",
"display": "standalone"
}

110
public/service-worker.js Normal file
View File

@ -0,0 +1,110 @@
// 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.
const CACHE_VERSION = 8;
const dataCacheName = `traintimesData-v${CACHE_VERSION}`;
const cacheName = `traintimePWA-final-${CACHE_VERSION}`;
const filesToCache = [
'/',
'/index.html',
'/service-worker.js',
'/manifest.json',
'/favicon.png',
'/browserconfig.xml',
'/build/bundle.css',
'/build/bundle.js',
'/build/fonts/fujicons.css',
'/build/fonts/fujicons.ttf',
'/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) {
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.warn('[Service Worker] Fetch', e.request.url);
const 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);
})
);
});

View File

@ -30,10 +30,7 @@
<style lang="scss" global>
@import "node_modules/spectre.css/src/spectre.scss";
/* @import "./css/custom.scss";*/
/*@import './fonts/fonts.css';
@import './fonts/gotham.css';*/
@import "./css/global.scss";
@import './fonts/fujicons.css';
.up,

View File

@ -2,6 +2,8 @@
import {pop} from 'svelte-spa-router';
export let page;
let showFav = false;
let titleText = 'Traintimes';
$: currentMode = (page === 'Home') ? 0 : 1;
@ -9,6 +11,10 @@
pop();
}
function goHome() {
}
</script>
@ -33,18 +39,21 @@
<section class="navbar-section">
{#if currentMode === 1}
<span on:click={goBack} class="">
<i class="fa-2x fa fa-back" style="color:white;"></i>
<i class="fa-2x fa fa-back-chevron" style="color:white;"></i>
</span>
{/if}
<span class="text-bold navbar-brand mx-2 text-uppercase">{titleText}</span>
<span class="text-bold navbar-brand mx-1 text-uppercase">{titleText}</span>
</section>
<section class="navbar-section">
<a href="/#/settings" class="btn btn-link text-secondary">Settings</a>
<a href="/#/favourites" class="btn btn-link text-secondary">Favourites</a>
<section class="navbar-section text-right">
<a href="/#/tweets" class="btn bg-primary ">Tweets</a>
<a href="/#/settings" class="btn bg-primary ">Settings</a>
{#if showFav}
<a href="/#/favourites" class="btn bg-primary">Favourites</a>
{/if}
</section>

View File

@ -1,21 +1,40 @@
<script>
import SettingsInput from "./SettingsInput.svelte";
import {state} from '../store/store';
let editorVisible = false;
let itemId;
let startStation;
let destStation;
let deleteEnabled = false;
let deleteEnabled = !(typeof startStation === 'string');
let canSave;
$: {
console.log('typeof startStation', typeof startStation);
canSave = (typeof startStation === 'object') && (typeof destStation === 'object');
}
function deleteItem() {
console.log('>> Delete item');
}
function closeEditor() {
console.log('>> Close item');
// console.log('>> Close item');
startStation = '';
destStation = '';
editorVisible = false;
}
function saveEditor() {
console.log('>> Save editor');
async function saveEditor() {
// console.log('>> Save editor');
await state.saveRoute({startStation, destStation});
closeEditor();
}
function newEditor() {
editorVisible = true;
}
</script>
@ -23,38 +42,42 @@
</style>
<span>SettingsEditor</span>
<div class="mui-container ">
{#if editorVisible}
<div class="container ">
<div class="mui-row card">
<div class="mui--text-headline">New Route</div>
<div class="mui--align-middle">
<div class='mui-col-xs-12 mui-col-md-5'>
<div class="text-subhead">New Route</div>
<div class="grid-3">
<div class=''>
<SettingsInput bind:returnValue={startStation} label="Departure Station" name="startStation"/>
</div>
<div class='mui-col-xs-12 mui-col-md-2'>
<i class="fa fa-thick-arrow fa-2x mui--align-middle">
<div class='text-center'>
<i class="fa fa-thick-arrow fa-2x mui--align-middle"></i>
</div>
<div class='mui-col-xs-12 mui-col-md-5'>
<div class=''>
<SettingsInput bind:returnValue={destStation} label="Destination Station" name="destStation"/>
</div>
</div>
<div class="my text-right">
<button class="btn btn-danger btn-sm" id="delete" type="button" disabled={deleteEnabled} on:click={deleteItem}>
Delete
</button>
<button class="btn btn-sm" type="button" on:click={closeEditor}>
Close
</button>
<button class="btn btn-primary btn-sm" id="save" type="button" on:click={saveEditor}>
<button class="btn btn-primary btn-sm" id="save" type="button" on:click={saveEditor} disabled={!canSave}>
Save
</button>
</div>
</div>
</div>
{:else}
<div>
<button class="btn btn-primary" id="new" type="button" on:click={newEditor}>
Add a new route
</button>
</div>
{/if}

View File

@ -3,8 +3,8 @@
import {onMount} from 'svelte';
import {searchStation} from '../libs/stations';
export let returnValue;
export let value;
export let returnValue = '';
let value = '';
export let name;
export let label;
@ -12,22 +12,32 @@
let debouncedDoSearch;
let searchResults = [];
$: {
if (returnValue === '') {
value = '';
}
}
// $: visible = (searchResults.length > 0) ?
onMount(async () => {
debouncedDoSearch = debounce(doSearch, 500);
debouncedDoSearch = debounce(doSearch, 750);
});
function doSearch() {
returnValue = '';
// console.log(`doSearch ${name}`, value)
if (value.length >= 2)
searchResults = searchStation(value);
else
searchResults = [];
}
function selectItem(e) {
// console.log('>> selectItem', e);
let [id, name] = e.target.dataset.content.split(',');
returnValue = id;
returnValue = {id,name};
value = name;
searchResults = [];
@ -41,7 +51,7 @@
<span class="mui-dropdown--right">
<label for={name}>{label}</label>
<input {name} on:keyup={debouncedDoSearch} bind:value />
<input autocomplete="off" {name} on:keyup={debouncedDoSearch} bind:value />
{#if searchResults.length > 0}
<ul class="mui-dropdown__menu mui--is-open">
{#each searchResults as item, index}

View File

@ -1,12 +1,20 @@
<script>
import {state} from '../store/store';
import SettingsListItem from "./SettingsListItem.svelte";
let _routes = [];
state.routes.subscribe(async (v) => {
_routes = v;
});
</script>
<style>
* {
background: #f55a4e;
padding: 3px;
}
</style>
<span>SettingsList</span>
{#each _routes as item, i (item.id)}
<SettingsListItem item={item} id={i}/>
{/each}

View File

@ -0,0 +1,60 @@
<script>
import { longpress } from '../libs/longpress.js';
import { slide } from 'svelte/transition';
import {state} from '../store/store.js';
export let item;
export let id;
let buttonsVisible = false;
let duration = 1000;
function deleteItem() {
console.log(`delete: ${id}`);
buttonsVisible = false;
state.deleteRoute(id);
}
function closeEditor() {
buttonsVisible = false
}
function saveEditor() {
}
</script>
<style>
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 1rem;
}
</style>
<div out:slide|local>
<div class="grid card ">
<div class="col-5">{item.startStation.name}</div>
<div class="col-1"><i class="fa fa-thick-arrow fa-1x mui--align-middle"></i></div>
<div class="col-5">{item.destStation.name}</div>
<div class="col-1"><button class="btn btn-sm" on:click="{() => buttonsVisible = !buttonsVisible}"><i class="fa fa-menu"></i></button></div>
</div>
{#if buttonsVisible}
<div class="my grid-3" transition:slide>
<button class="btn btn-danger btn-sm" id="delete" type="button" on:click={deleteItem}>
Delete
</button>
<button class="btn btn-primary btn-sm" id="save" type="button" on:click={saveEditor}>
Edit
</button>
<button class="btn btn-sm" type="button" on:click={closeEditor}>
Close
</button>
</div>
{/if}
</div>

View File

@ -1,8 +1,10 @@
<script>
import {onMount, onDestroy} from 'svelte';
import axios from 'axios';
import { fade } from 'svelte/transition';
import axios from 'redaxios';
import {push} from 'svelte-spa-router';
import {state} from '../store/store';
import reducer from '../libs/reducer';
export let fromStation;
@ -10,11 +12,23 @@
let list = []
let otherDetails = {};
let baseUrl = 'http://localhost:8100';
let baseUrl = state.getBaseUrl();
let doUpdate = true;
let fetchInterval;
onMount(async () => {
await fetchData();
fetchInterval = setInterval(async () => {
console.log('Timetable update')
await fetchData();
}, 120000);
});
onDestroy(async () => {
clearInterval(fetchInterval);
});
async function fetchData() {
@ -23,8 +37,9 @@
const url = baseUrl.concat(routeUrl)
await axios.get(url)
.then((d) => {
list = reducer.reduceTrainTimetable(d.data)
otherDetails = reducer.reduceOtherDetails(d.data)
const data = JSON.parse(d.data);
list = reducer.reduceTrainTimetable(data)
otherDetails = reducer.reduceOtherDetails(data)
})
}
}
@ -42,10 +57,10 @@
<div>
<section>
{#if otherDetails.nrMessagesExist === true}
<div class="mui--bg-danger mui--text-white nrccAlert" style="padding:2px;">
<div class="nrccAlert" style="padding:2px;">
<ul>
{#each otherDetails.nrMessages as item}
<li><i class="fa fa-info mui--align-middle"></i> {item.msg}
<li class="alert bg-danger"><i class="fa fa-info mui--align-middle"></i> {item.msg}
{#if item.link}
<a href={item.link}>{item.linkText}</a>
{/if}
@ -59,9 +74,9 @@
{#if list.length > 0}
{#each list as item}
<div class="mui-row card mui--align-bottom">
<div class="mui-col-xs-5 mui-col-md-4 mui--align-middle">
{#each list as item, i (item.serviceIdUrlSafe)}
<div class="grid card mui--align-bottom" transition:fade|local>
<div class="col-5 mui--align-middle">
<span on:click={viewService(item.serviceIdUrlSafe)}>{item.location}</span>
<span class="mui--text-accent">{item.carriageCount}</span>
<div>
@ -70,13 +85,13 @@
{/if}
</div>
</div>
<div class="mui-col-xs-2 mui-col-md-3 mui--text-center mui--align-middle time">{item.time}</div>
<div class="col-2 text-center mui--align-middle time">{item.time}</div>
{#if item.isCancelled}
<div class="mui-col-xs-5 mui-col-md-5 mui--text-center mui--align-middle delayed"><i class="fa fa-alert fa-1x mui--align-middle"></i>{item.cancel}</div>
<div class="col-5 text-center mui--align-middle delayed"><i class="fa fa-alert fa-1x mui--align-middle"></i>{item.cancel}</div>
{:else}
<div class="mui-col-xs-3 mui-col-md-3 mui--text-center mui--align-middle {item.statusMode}">{item.status}</div>
<div class="mui-col-xs-2 mui-col-md-2 mui--text-center mui--align-middle">{item.platform}</div>
<div class="col-3 text-center mui--align-middle {item.statusMode}">{item.status}</div>
<div class="col-2 text-center mui--align-middle">{item.platform}</div>
{/if}
</div>
{/each}

View File

@ -1,8 +1,11 @@
<script>
import { fade } from 'svelte/transition';
import {findStation} from '../libs/stations'
import {minuteFloor, LocalStorage} from '../libs/utils'
import {push} from 'svelte-spa-router';
import axios from 'axios';
import axios from 'redaxios';
import {state} from '../store/store';
import {onMount, onDestroy} from 'svelte';
@ -12,7 +15,7 @@
let startStationName;
let destStationName;
let url;
let baseUrl = 'http://localhost:8100';
let baseUrl = state.getBaseUrl();
let displayTime;
let trainData = {eta: 'OFF', sta: 'OFF'};
let status;
@ -81,20 +84,51 @@
const workingUrl = url.concat(`&mh=${minuteHash}`)
axios.get(workingUrl)
.then((d) => {
trainData = {...d.data}
trainData = {...JSON.parse(d.data)};
})
}
</script>
<style>
.entry {
.TRcard {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 0rem;
z-index: 1;
width: 100%;
opacity: 0.9;
margin-bottom: 0.4rem;
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);
}
.TRcard .entry {
align-items: left;
display: flex;
display: -ms-flexbox;
/* -ms-flex: 1 0 0;
flex: 1 0 0;*/
-ms-flex-align: center;
flex-direction: column;
}
.TRcard .entry:not(:first-child):last-child {
-ms-flex-pack: end;
justify-content: flex-end;
align-items: right;
}
.entryOld {
height: 36px;
margin: 6px 0;
vertical-align: middle;
}
.TRcard {
.TRcardOld {
position: relative;
background-color: #fff;
min-height: 48px;
@ -111,11 +145,11 @@
</style>
<div class="columns TRcard">
<div class='column col-7 entry'>
<div class='entry ml-1'>
<div>{startStationName}</div>
<div>{destStationName}</div>
</div>
<div class='column col-5 text-right entry'>
<div class='entry text-right'>
<span class="btn {status}" on:click={onClick}>{displayTime}</span>
</div>
</div>

View File

@ -1,22 +1,23 @@
<script>
import {onMount, onDestroy} from 'svelte';
import {minuteFloor, LocalStorage} from '../libs/utils'
import axios from 'axios';
import axios from 'redaxios';
import {state} from '../store/store';
import reducer from '../libs/reducer';
export let serviceId;
let list = [];
let baseUrl = 'http://localhost:8100';
let baseUrl = state.getBaseUrl();
let doUpdate = true;
let serviceInterval;
onMount(async () => {
fetchServiceData();
await fetchServiceData();
serviceInterval = setInterval(() => {
console.log('Do service update')
fetchServiceData()
serviceInterval = setInterval(async () => {
console.log('Service update')
await fetchServiceData()
}, 120000);
});
@ -24,17 +25,14 @@
clearInterval(serviceInterval);
});
function fetchServiceData() {
console.log('>> TrainService: fetchServiceData');
// http://localhost:8100/getservice?serviceid=TDKWvQdeuviRyNYP7lk7gA
async function fetchServiceData() {
if (doUpdate === true) {
const routeUrl = `/getservice?serviceid=${serviceId}`
const url = baseUrl.concat(routeUrl)
axios.get(url)
await axios.get(url)
.then((d) => {
console.log(d);
list = reducer.reduceTrainService(d.data);
const data = JSON.parse(d.data);
list = reducer.reduceTrainService(data);
})
}
}
@ -45,19 +43,19 @@
</style>
<section>
<div class="mui-row card mui--align-bottom">
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle">Station</div>
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle">Due</div>
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle">Estimated</div>
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle">Arrived</div>
<div class="grid card text-subhead">
<div class="col-3 text-center">Station</div>
<div class="col-3 text-center">Due</div>
<div class="col-3 text-center">Estimated</div>
<div class="col-3 text-center">Arrived</div>
</div>
{#if list.length > 0}
{#each list as item}
<div class="mui-row card mui--align-bottom {item.classCancel}">
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle">{item.locationName}</div>
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle">{item.st}</div>
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle {item.etMode}">{item.et}</div>
<div class="mui-col-xs-3 mui-col-md-3 mui--align-middle {item.atMode}">{item.at}</div>
<div class="grid card {item.classCancel}">
<div class="col-3 text-center">{item.locationName}</div>
<div class="col-3 text-center">{item.st}</div>
<div class="col-3 text-center {item.etMode}">{item.et}</div>
<div class="col-3 text-center {item.atMode}">{item.at}</div>
</div>
{/each}
{/if}

View File

@ -0,0 +1,64 @@
<script>
import {onMount, onDestroy} from 'svelte';
import {state} from '../store/store';
export let id;
let tweetData;
let tweet;
let inReply = false;
let tweetBody = ''
$: {
if (tweet) {
inReply = (tweet && tweet.in_reply_to_status_id !== null);
tweetBody = (tweet && tweet.truncated) ? tweet.extended_tweet.full_text : tweet.text;
}
}
onMount(async () => {
tweetData = state.getTweetByID(id);
tweet = JSON.parse(tweetData.tweet);
});
function viaImgCache(url) {
const cache = (true) ? 'https://image.silvrtree.co.uk/48,fit,q80/' : '';
return `${cache}${url}`;
}
</script>
<style>
.avatar {
max-width: 48px;
max-height: 48px;
border-radius: 0.1rem;
}
</style>
{#if tweet}
<div class="card">
<blockquote>
<div class="tweet-header">
<div class="grid">
<img class='avatar col-2' src={viaImgCache(tweet.user.profile_image_url_https)} alt="@{tweet.user.screen_name}"/>
<div class="col-10 ml-2">
<div class="text-subhead">{tweet.user.name}</div>
<div>@{tweet.user.screen_name}</div>
</div>
</div>
</div>
{#if inReply}
<div>
Replying to @{tweet.in_reply_to_screen_name}
</div>
{/if}
<div class="tweetBody">
{tweetBody}
</div>
<div>
<small class="text-highlight2">{tweet.created_at}</small>
</div>
</blockquote>
</div>
{/if}

View File

@ -0,0 +1,199 @@
<script>
import {state} from '../store/store';
import {onMount, onDestroy} from 'svelte';
let _following;
let mounted = false;
onMount(async () => {
_following = state.getTwitterFollowing();
mounted=(Object.keys(_following).length > 0);
});
onDestroy(async () => {
state.saveTwitterFollowing(_following);
});
state.twitterFollowing.subscribe(async (v) => {
_following = v;
mounted=(Object.keys(_following).length > 0);
});
</script>
<style>
label {
font-weight: normal;
}
</style>
<div class="container ">
<div class="text-dark text-subhead">Twitter</div>
{#if mounted}
<div class="grid-1">
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.nationalrailenq.follow}>
@nationalrailenq
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.networkrail.follow}>
@networkrail
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailSCOT.follow}>
@NetworkRailSCOT
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.AvantiWestCoast.follow}>
@AvantiWestCoast
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.CalSleeper.follow}>
@CalSleeper
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.CrossCountryUK.follow}>
@CrossCountryUK
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.Eurostar.follow}>
@Eurostar
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.EurostarUK.follow}>
@EurostarUK
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.GatwickExpress.follow}>
@GatwickExpress
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.GlasgowSubway.follow}>
@GlasgowSubway
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.GWRHelp.follow}>
@GWRHelp
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.HeathrowExpress.follow}>
@HeathrowExpress
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.LNER.follow}>
@LNER
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.LNRailway.follow}>
@LNRailway
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.northernassist.follow}>
@northernassist
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.ScotRail.follow}>
@ScotRail
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.Stansted_Exp.follow}>
@Stansted_Exp
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.TfL.follow}>
@TfL
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailBHM.follow}>
@NetworkRailBHM
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailEDB.follow}>
@NetworkRailEDB
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailEUS.follow}>
@NetworkRailEUS
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailGLC.follow}>
@NetworkRailGLC
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailKGX.follow}>
@NetworkRailKGX
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailLST.follow}>
@NetworkRailLST
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailMAN.follow}>
@NetworkRailMAN
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.NetworkRailVIC.follow}>
@NetworkRailVIC
</label>
</div>
<div>
<label class="label-body">
<input type="checkbox" bind:checked={_following.BTPScotland.follow}>
@BTPScotland
</label>
</div>
</div>
{/if}
</div>

277
src/css/entireframework.min.css vendored Normal file
View File

@ -0,0 +1,277 @@
/* Copyright 2014 Owen Versteeg; MIT licensed */
body, textarea, input, select {
background: 0;
border-radius: 0;
font: 16px sans-serif;
margin: 0
}
.smooth {
transition: all .2s
}
.btn, .nav a {
text-decoration: none
}
.container {
margin: 0 20px;
width: auto
}
label > * {
display: inline
}
form > * {
display: block;
margin-bottom: 10px
}
.btn {
background: #999;
border-radius: 6px;
border: 0;
color: #fff;
cursor: pointer;
display: inline-block;
margin: 2px 0;
padding: 12px 30px 14px
}
.btn:hover {
background: #888
}
.btn:active, .btn:focus {
background: #777
}
.btn-a {
background: #0ae
}
.btn-a:hover {
background: #09d
}
.btn-a:active, .btn-a:focus {
background: #08b
}
.btn-b {
background: #3c5
}
.btn-b:hover {
background: #2b4
}
.btn-b:active, .btn-b:focus {
background: #2a4
}
.btn-c {
background: #d33
}
.btn-c:hover {
background: #c22
}
.btn-c:active, .btn-c:focus {
background: #b22
}
.btn-sm {
border-radius: 4px;
padding: 10px 14px 11px
}
.row {
margin: 1% 0;
overflow: auto
}
.col {
float: left
}
.table, .c12 {
width: 100%
}
.c11 {
width: 91.66%
}
.c10 {
width: 83.33%
}
.c9 {
width: 75%
}
.c8 {
width: 66.66%
}
.c7 {
width: 58.33%
}
.c6 {
width: 50%
}
.c5 {
width: 41.66%
}
.c4 {
width: 33.33%
}
.c3 {
width: 25%
}
.c2 {
width: 16.66%
}
.c1 {
width: 8.33%
}
h1 {
font-size: 3em
}
.btn, h2 {
font-size: 2em
}
.ico {
font: 33px Arial Unicode MS, Lucida Sans Unicode
}
.addon, .btn-sm, .nav, textarea, input, select {
outline: 0;
font-size: 14px
}
textarea, input, select {
padding: 8px;
border: 1px solid #ccc
}
textarea:focus, input:focus, select:focus {
border-color: #5ab
}
textarea, input[type=text] {
-webkit-appearance: none;
width: 13em
}
.addon {
padding: 8px 12px;
box-shadow: 0 0 0 1px #ccc
}
.nav, .nav .current, .nav a:hover {
background: #000;
color: #fff
}
.nav {
height: 24px;
padding: 11px 0 15px
}
.nav a {
color: #aaa;
padding-right: 1em;
position: relative;
top: -1px
}
.nav .pagename {
font-size: 22px;
top: 1px
}
.btn.btn-close {
background: #000;
float: right;
font-size: 25px;
margin: -54px 7px;
display: none
}
@media (min-width: 1310px) {
.container {
margin: auto;
width: 1270px
}
}
@media (max-width: 870px) {
.row .col {
width: 100%
}
}
@media (max-width: 500px) {
.btn.btn-close {
display: block
}
.nav {
overflow: hidden
}
.pagename {
margin-top: -11px
}
.nav:active, .nav:focus {
height: auto
}
.nav div:before {
background: #000;
border-bottom: 10px double;
border-top: 3px solid;
content: '';
float: right;
height: 4px;
position: relative;
right: 3px;
top: 14px;
width: 20px
}
.nav a {
padding: .5em 0;
display: block;
width: 50%
}
}
.table th, .table td {
padding: .5em;
text-align: left
}
.table tbody > :nth-child(2n-1) {
background: #ddd
}
.msg {
padding: 1.5em;
background: #def;
border-left: 5px solid #59d
}

896
src/css/global.scss Normal file
View File

@ -0,0 +1,896 @@
@import url('https://fonts.googleapis.com/css?family=Roboto+Condensed');
/* Global Styles */
:root {
--primary-color: #607D8B;
--dark-color: #294c5d;
--light-color: #CFD8DC;
--danger-color: #dc3545;
--success-color: #28a745;
--highlight-color: #dcc894;
--highlight-color2: #dca394;
--navbar-height: 4rem;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Roboto Condensed', sans-serif;
font-size: 1rem;
line-height: 1.6;
background-color: #fff;
color: #333;
}
a {
color: var(--primary-color);
text-decoration: none;
}
a:hover {
color: #666;
}
a.active{
color: var(--highlight-color);
}
ul {
list-style: none;
}
img {
width: 100%;
}
.dataRow {
cursor: pointer;
}
/* Utilities */
.container {
max-width: 1100px;
margin: auto;
overflow: hidden;
padding: 0 2rem;
}
/* Text Styles*/
.x-large {
font-size: 4rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.large {
font-size: 3rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.lead {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.text-center {
text-align: center;
}
.text-primary {
color: var(--primary-color);
}
.text-dark {
color: var(--dark-color);
}
.text-success {
color: var(--success-color);
}
.text-danger {
color: var(--danger-color);
}
.text-highlight {
color: var(--highlight-color);
}
.text-highlight2 {
color: var(--highlight-color2);
}
.text-center {
text-align: center;
}
.text-right {
text-align: right;
}
.text-left {
text-align: left;
}
.text-lowercase {
text-transform: lowercase;
}
.text-uppercase {
text-transform: uppercase;
}
.text-capitalize {
text-transform: capitalize;
}
.text-title, h3 {
font-weight: 400;
font-size: 20px;
line-height: 28px;
}
.text-subhead, h4 {
font-weight: 400;
font-size: 16px;
line-height: 24px;
}
.text-body2, h5 {
font-weight: 500;
font-size: 14px;
line-height: 24px;
}
.text-body1 {
font-weight: 400;
font-size: 14px;
line-height: 20px;
}
.text-caption {
font-weight: 400;
font-size: 12px;
line-height: 16px;
}
/* Center All */
.all-center {
display: flex;
flex-direction: column;
width: 100%;
margin: auto;
justify-content: center;
align-items: center;
text-align: center;
}
/* Cards */
.card {
padding: 1rem;
border: #ccc 1px dotted;
margin: 0.7rem 0;
}
/* List */
.list {
margin: 0.5rem 0;
}
.list li {
padding-bottom: 0.3rem;
}
/* Padding */
.p {
padding: 0.5rem;
}
.p-1 {
padding: 1rem;
}
.p-2 {
padding: 2rem;
}
.p-3 {
padding: 3rem;
}
.py {
padding: 0.5rem 0;
}
.py-1 {
padding: 1rem 0;
}
.py-2 {
padding: 2rem 0;
}
.py-3 {
padding: 3rem 0;
}
/* Margin */
.m {
margin: 0.5rem;
}
.m-1 {
margin: 1rem;
}
.mb-1 {
margin-bottom: .2rem !important;
}
.ml-1 {
margin-left: .2rem !important;
}
.mr-1 {
margin-right: .2rem !important;
}
.mt-1 {
margin-top: .2rem !important;
}
.mx-1 {
margin-left: 1rem !important;
margin-right: 1rem !important;
}
.m-2 {
margin: 2rem;
}
.mb-2 {
margin-bottom: .4rem !important;
}
.ml-2 {
margin-left: .4rem !important;
}
.mr-2 {
margin-right: .4rem !important;
}
.mt-2 {
margin-top: .4rem !important;
}
.mx-2 {
margin-left: 2rem !important;
margin-right: 2rem !important;
}
.m-3 {
margin: 3rem;
}
.my {
margin: 0.5rem 0;
}
.my-1 {
margin: 1rem 0;
}
.my-2 {
margin: 2rem 0;
}
.my-3 {
margin: 3rem 0;
}
.grid {
display: flex;
display: -ms-flexbox;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-left: -.4rem;
margin-right: -.4rem;
}
/* Grid */
.grid-1 {
display: grid;
grid-template-columns: repeat(1, 1fr);
grid-gap: 1rem;
}
.grid-2 {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 1rem;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 1rem;
}
.grid-4 {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 1rem;
}
.column,
/*.columns {
margin-left: 4%; }
.column:first-child,
.columns:first-child {
margin-left: 0; }*/
.col-1 {
width: 8.33333%;
}
.col-2 {
width: 16.66667%;
}
.col-3 {
width: 25%;
}
.col-4 {
width: 33.33333%;
}
.col-5 {
width: 41.66667%;
}
.col-6 {
width: 50%;
}
.col-7 {
width: 58.33333%;
}
.col-8 {
width: 66.66667%;
}
.col-9 {
width: 75%;
}
.col-10 {
width: 83.33333%;
}
.col-11 {
width: 91.66667%;
}
.col-12 {
width: 100%;
margin-left: 0;
}
.col-1-3rd {
width: 30.6666666667%;
}
.col-2-3rd {
width: 65.3333333333%;
}
.col-half {
width: 48%;
}
/* Offsets */
.offset-1-col {
margin-left: 8.66666666667%;
}
.offset-2-col {
margin-left: 17.3333333333%;
}
.offset-3-col {
margin-left: 26%;
}
.offset-4-col {
margin-left: 34.6666666667%;
}
.offset-5-col {
margin-left: 43.3333333333%;
}
.offset-6-col {
margin-left: 52%;
}
.offset-7-col {
margin-left: 60.6666666667%;
}
.offset-8-col {
margin-left: 69.3333333333%;
}
.offset-9-col {
margin-left: 78.0%;
}
.offset-10-col {
margin-left: 86.6666666667%;
}
.offset-11-col {
margin-left: 95.3333333333%;
}
.offset-1-3rd-col {
margin-left: 34.6666666667%;
}
.offset-2-3rd-col {
margin-left: 69.3333333333%;
}
.offset-half-col {
margin-left: 52%;
}
.btn {
display: inline-block;
background: var(--light-color);
color: #333;
padding: 0.4rem 1.3rem;
font-size: 1rem;
border: none;
cursor: pointer;
margin-right: 0.5rem;
transition: opacity 0.2s ease-in;
outline: none;
}
.btn-link {
background: none;
padding: 0;
margin: 0;
}
.btn-block {
display: block;
width: 100%;
}
.btn-sm {
font-size: 0.8rem;
padding: 0.3rem 1rem;
margin-right: 0.2rem;
}
.badge {
display: inline-block;
font-size: 0.6rem;
padding: 0.1rem 0.4rem;
text-align: center;
margin: 0.3rem;
background: var(--light-color);
color: #333;
border-radius: 3px;
}
.alert {
padding: 0.7rem;
margin: 1rem 0;
opacity: 0.9;
background: var(--light-color);
color: #333;
}
.btn-primary,
.bg-primary,
.badge-primary,
.alert-primary {
background: var(--primary-color);
color: #fff;
}
.btn-light,
.bg-light,
.badge-light,
.alert-light {
background: var(--light-color);
color: #333;
}
.btn-dark,
.bg-dark,
.badge-dark,
.alert-dark {
background: var(--dark-color);
color: #fff;
}
.btn-danger,
.bg-danger,
.badge-danger,
.alert-danger {
background: var(--danger-color);
color: #fff;
}
.btn-success,
.bg-success,
.badge-success,
.alert-success {
background: var(--success-color);
color: #fff;
}
.btn-white,
.bg-white,
.badge-white,
.alert-white {
background: #fff;
color: #333;
border: #ccc solid 1px;
}
.btn:disabled {
cursor: not-allowed;
pointer-events: none;
opacity: 0.60;
-webkit-box-shadow: none;
box-shadow: none;
}
.btn:enabled:hover {
opacity: 0.8;
}
.bg-light,
.badge-light {
border: #ccc solid 1px;
}
.round-img {
border-radius: 50%;
}
/* Forms */
input {
margin: .2rem 0;
}
.form-text {
display: block;
margin-top: 0.3rem;
color: #888;
}
input[type='text'],
input[type='email'],
input[type='password'],
input[type='date'],
select,
textarea {
display: block;
width: 100%;
padding: 0.1rem;
/*font-size: 1.2rem;*/
border: 1px solid #ccc;
}
input[type='submit'],
button {
font: inherit;
}
label,
legend {
display: block;
margin-bottom: .1rem;
font-weight: 600; }
input[type="checkbox"],
input[type="radio"] {
display: inline; }
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal;
background-color: #dcc894;
}
table th,
table td {
padding: 1rem;
text-align: left;
}
table th {
background: var(--light-color);
}
/* Navbar */
.navbar {
position:fixed;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 0rem;
z-index: 2;
width: 100%;
opacity: 0.9;
margin-bottom: 1rem;
min-height: var(--navbar-height);
}
.navbar ul {
display: flex;
}
.navbar a {
color: #fff;
padding: 0.45rem;
margin: 0 0.25rem;
}
.navbar a:hover {
color: var(--light-color);
}
.navbar .welcome span {
margin-right: 0.6rem;
}
.navbar .navbar-section {
align-items: center;
display: flex;
display: -ms-flexbox;
-ms-flex: 1 0 0;
flex: 1 0 0;
-ms-flex-align: center;
}
.navbar .navbar-section:not(:first-child):last-child {
-ms-flex-pack: end;
justify-content: flex-end;
}
.navbar .navbar-brand {
font-size: 125%;
font-weight: bold;
}
/* Mobile Styles */
@media (max-width: 700px) {
.hide-sm {
display: none;
}
.grid-2,
.grid-3,
.grid-4 {
grid-template-columns: 1fr;
}
/* Text Styles */
.x-large {
font-size: 3rem;
}
.large {
font-size: 2rem;
}
.lead {
font-size: 1rem;
}
/* Navbar */
.navbar {
display: flex;
text-align: center;
}
.navbar ul {
text-align: center;
justify-content: center;
}
}
/*:root {
--primary-color: #64B5F6;
--dark-color: #333333;
--light-color: #f4f4f4;
--danger-color: #dc3545;
--success-color: #28a745;
--medium-color: #999999;
}*/
.table-responsive {
display: block;
overflow-x: auto;
width: 100%;
}
.cardV2 {
border-radius: 4px;
background-color: #fff;
box-shadow: 0 0 4px 0 rgba(0,0,0,.14), 0 3px 4px 0 rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);
/*display: flex;
flex-direction: column;*/
min-width: 0;
/*position: relative;
word-wrap: break-word;*/
}
table {
max-width: 100%;
width: 100%;
border: 0;
margin-bottom: 1rem;
border-collapse: collapse;
}
tr {
border-top: 1px solid #ccc;
}
tbody tr:nth-of-type(odd){
background-color: rgba(0,0,0,0.04);
}
tbody td {
border-top: 1px solid #e1e1e1;
}
hr {
margin-top: 2.2rem;
margin-bottom: 2rem;
border-width: 0;
border-top: 1px solid var(--dark-color); }
.modalWindow {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.2);
z-index: 99999;
opacity:0;
pointer-events: none;
text-align:center;
}
.modalWindow:target {
opacity:1;
pointer-events: auto;
}
.modalWindow > div {
width: 500px;
position: relative;
margin: 10% auto;
background: #fff;
}
header + div.container {
position: relative;
top:var(--navbar-height);
min-height: calc(100vh - var(--navbar-height));
}
/**
* MUI Dropdown module
*/
.mui-dropdown {
display: inline-block;
position: relative;
}
[data-mui-toggle="dropdown"] {
outline: 0;
}
.mui-dropdown__menu {
position: absolute;
display: none;
min-width: 160px;
padding: 5px 3px;
margin: 2px 0 0;
list-style: none;
font-size: 14px;
text-align: left;
background-color: #FFF;
border-radius: 2px;
z-index: 1;
background-clip: padding-box;
border: 1px solid var(--light-color);
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);
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.mui-dropdown__menu {
border-top: 1px solid rgba(0, 0, 0, 0.12);
border-left: 1px solid rgba(0, 0, 0, 0.12);
}
}
@supports (-ms-ime-align: auto) {
.mui-dropdown__menu {
border-top: 1px solid rgba(0, 0, 0, 0.12);
border-left: 1px solid rgba(0, 0, 0, 0.12);
}
}
.mui-dropdown__menu.mui--is-open {
display: block;
}
.mui-dropdown__menu > li > a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.429;
color: rgba(0, 0, 0, 0.87);
text-decoration: none;
white-space: nowrap;
}
.mui-dropdown__menu > li > a:hover, .mui-dropdown__menu > li > a:focus {
text-decoration: none;
color: rgba(0, 0, 0, 0.87);
background-color: #EEEEEE;
}
.mui-dropdown__menu > .mui--is-disabled > a, .mui-dropdown__menu > .mui--is-disabled > a:hover, .mui-dropdown__menu > .mui--is-disabled > a:focus {
color: #EEEEEE;
}
.mui-dropdown__menu > .mui--is-disabled > a:hover, .mui-dropdown__menu > .mui--is-disabled > a:focus {
text-decoration: none;
background-color: transparent;
background-image: none;
cursor: not-allowed;
}
.mui-dropdown__menu--right {
left: auto;
right: 0;
}
.mui-dropdown--up > .mui-dropdown__menu {
margin: 0 0 2px;
}
.mui-dropdown--right > .mui-dropdown__menu {
margin: 0 0 0 2px;
}
.mui-dropdown--left > .mui-dropdown__menu {
margin: 0 2px 0 0;
}

29
src/libs/longpress.js Normal file
View File

@ -0,0 +1,29 @@
export function longpress(node, duration) {
let timer;
const handleMousedown = () => {
console.log('>> handleMousedown');
timer = setTimeout(() => {
node.dispatchEvent(
new CustomEvent('longpress')
);
}, duration);
};
const handleMouseup = () => {
clearTimeout(timer);
};
node.addEventListener('mousedown', handleMousedown);
node.addEventListener('mouseup', handleMouseup);
return {
update(newDuration) {
duration = newDuration;
},
destroy() {
node.removeEventListener('mousedown', handleMousedown);
node.removeEventListener('mouseup', handleMouseup);
}
};
}

View File

@ -41,7 +41,7 @@ const reducer = {
const symbol = ['💠', '🚉'];
if (typeof data === 'object' && data !== null) {
console.log('>> reduceTrainService');
console.log(data);
// console.log(data);
if (typeof data.trainServices === 'object' && data.trainServices !== null)
for (const item of data.trainServices) {
// console.log(item)

View File

@ -1,5 +1,7 @@
import App from './App.svelte';
import { state } from './store/store';
const app = new App({
'target': document.body,
'props': {
@ -7,4 +9,26 @@ const app = new App({
}
});
if ('serviceWorker' in navigator) {
//
navigator.serviceWorker.ready.then(function(reg) {
console.warn('Ready??', reg);
// main();
});
window.addEventListener('load', function() {
navigator.serviceWorker
.register('./service-worker.js')
.then((r) => {
console.warn('Service Worker Registered', r.scope);
})
.catch((error) => {
// registration failed
console.error(`Registration failed with ${ error}`);
});
});
//
}
export default app;

View File

@ -0,0 +1,25 @@
<script>
</script>
<style>
* {
background: #f55a4e;
padding: 3px;
}
</style>
<div class="container">
<h1>Favourites</h1>
<div class="grid-2">
<div>a</div>
<div>b</div>
<div>c</div>
<div>d</div>
</div>
</div>

View File

@ -1,10 +1,24 @@
<script>
import TrainRoute from "../components/TrainRoute.svelte";
import {state} from '../store/store';
let _routes = [];
state.routes.subscribe(async (v) => {
_routes = v;
});
</script>
<div class="container">
<TrainRoute destStation="glq" startStation="dbe"/>
{#each _routes as item}
<TrainRoute destStation={item.destStation.id} startStation={item.startStation.id}/>
{/each}
<!-- <TrainRoute destStation="glq" startStation="dbe"/>
<TrainRoute destStation="dbe" startStation="glq"/>
<TrainRoute destStation="glc" startStation="ptk"/>
<TrainRoute destStation="ptk" startStation="dbe"/>
@ -14,7 +28,7 @@
<TrainRoute destStation="glc" startStation="bhi"/>
<TrainRoute destStation="bhi" startStation="glc"/>
<TrainRoute destStation="dbe" startStation="dmr"/>
<TrainRoute destStation="dmr" startStation="glc"/>
<TrainRoute destStation="dmr" startStation="glc"/>-->
</div>

View File

@ -10,8 +10,7 @@
</style>
<div class="mui--appbar-height"></div>
<div class="mui-container">
<div class="container">
<TrainService {serviceId}/>
</div>

View File

@ -1,6 +1,7 @@
<script>
import SettingsEditor from "../components/SettingsEditor.svelte";
import SettingsList from "../components/SettingsList.svelte";
import TwitterSettings from "../components/TwitterSettings.svelte";
</script>
@ -9,8 +10,10 @@
</style>
<div class="container">
<span>Settings</span>
<div class="text-dark text-subhead">Settings</div>
<SettingsEditor/>
<SettingsList/>
<hr/>
<TwitterSettings/>
</div>

View File

@ -22,7 +22,7 @@
<div class="container">
<div>
<div class="mui--text-center mui--text-accent">{fromStationName} TO {destStationName}</div>
<div class="text-center text-dark text-subhead">{fromStationName} TO {destStationName}</div>
<TimetableList {fromStation} {destStation}/>
<!--<timetable-list :fromStation="$route.params.fromStation" :destStation="$route.params.destStation"/>-->

39
src/pages/Twitter.svelte Normal file
View File

@ -0,0 +1,39 @@
<script>
import {onMount, onDestroy} from 'svelte';
import {state} from '../store/store';
import Twitter from "../components/Twitter.svelte";
let _tweets = [];
let serviceInterval;
onMount(async () => {
await state.getTweets();
serviceInterval = setInterval(async () => {
console.log('Twitter update')
await state.getTweets();
}, 300000);
});
onDestroy(async () => {
clearInterval(serviceInterval);
});
state.tweetList.subscribe(async (v) => {
_tweets = v;
});
</script>
<style>
</style>
<div class="container">
<div class="text-subhead">Twitter</div>
{#each _tweets as item, i (item)}
<Twitter id={item}/>
{/each}
</div>

View File

@ -11,6 +11,8 @@ import Home from './pages/Home.svelte';
import Service from './pages/Service.svelte';
import Timetable from './pages/Timetable.svelte';
import Settings from './pages/Settings.svelte';
import Favourites from './pages/Favourites.svelte';
import Twitter from './pages/Twitter.svelte';
import NotFound from './pages/NotFound.svelte';
const routes = new Map();
@ -18,6 +20,8 @@ routes.set('/', Home);
routes.set('/timetable/:fromStation/:destStation', Timetable);
routes.set('/service/:serviceId', Service);
routes.set('/settings', Settings);
routes.set('/favourites', Favourites);
routes.set('/tweets', Twitter);
routes.set('*', NotFound);
export default routes;

281
src/store/store.js Normal file
View File

@ -0,0 +1,281 @@
/**
* Created by WebStorm.
* User: martin
* Date: 28/04/2020
* Time: 11:28
*/
import axios from 'redaxios';
import { writable, get } from 'svelte/store';
import localforage from 'localforage';
const baseUrl = (ENV === 'production') ? (`${location.protocol }//${ location.hostname}`) : 'http://localhost:8100';
let started = false;
const twitterAccounts = {
'nationalrailenq': {
'id': 33546465,
'follow': false
},
'networkrail': {
'id': 365344176,
'follow': false
},
'NetworkRailSCOT': {
'id': 402687948,
'follow': false
},
'AvantiWestCoast': {
'id': 1143560758476906497,
'follow': false
},
'CalSleeper': {
'id': 2870293725,
'follow': false
},
'CrossCountryUK': {
'id': 153368708,
'follow': false
},
'Eurostar': {
'id': 98412169,
'follow': false
},
'EurostarUK': {
'id': 59742254,
'follow': false
},
'GatwickExpress': {
'id': 163816182,
'follow': false
},
'GlasgowSubway': {
'id': 224607925,
'follow': false
},
'GWRHelp': {
'id': 15589815,
'follow': false
},
'HeathrowExpress': {
'id': 20240678,
'follow': false
},
'LNER': {
'id': 313306238,
'follow': false
},
'LNRailway': {
'id': 910487328627535872,
'follow': false
},
'northernassist': {
'id': 194512268,
'follow': false
},
'ScotRail': {
'id': 61569136,
'follow': false
},
'Stansted_Exp': {
'id': 257511611,
'follow': false
},
'TfL': {
'id': 47319664,
'follow': false
},
'NetworkRailBHM': {
'id': 583910976,
'follow': false
},
'NetworkRailEDB': {
'id': 586614081,
'follow': false
},
'NetworkRailEUS': {
'id': 581807264,
'follow': false
},
'NetworkRailGLC': {
'id': 421061171,
'follow': false
},
'NetworkRailKGX': {
'id': 459192871,
'follow': false
},
'NetworkRailLST': {
'id': 581826097,
'follow': false
},
'NetworkRailMAN': {
'id': 583895871,
'follow': false
},
'NetworkRailVIC': {
'id': 587354752,
'follow': false
},
'BTPScotland': {
'id': 957256160,
'follow': false
}
};
const state = {
'twitterFollowing': writable({}),
'twitterFollowingList' : writable([]),
'tweetList' : writable([]),
'tweets' : writable([]),
'favourites' : writable([]),
'routes' : writable([]),
'routeIndex' : writable(0),
incrementRouteIndex() {
this.routeIndex.update(v => v + 1);
},
async saveRoute(newRoute) {
// console.log('Newroute', newRoute);
const _newRoute = { ...newRoute };
_newRoute.id = get(this.routeIndex);
this.routes.update((v) => {
// console.log('update', [...v, _newRoute]);
return [...v, _newRoute];
});
this.incrementRouteIndex();
},
deleteRoute(id) {
const pre = get(this.routes).slice(0, id);
const post = get(this.routes).slice(id + 1);
this.routes.set([...pre, ...post]);
},
async getTweets() {
// console.log('>> getTweets');
// console.log('>> state.twitterFollowingList', get(state.twitterFollowingList));
const list = get(state.twitterFollowingList).map((item) => {
return item[1].id;
})/* .join(',')*/;
// console.log('>> actual ids', list);
// const routeUrl = `/twitter?t=${list}`;
const routeUrl = '/twitter';
const twitterUrl = baseUrl.concat(routeUrl);
const postReq = {
'method':'post',
'url': twitterUrl,
'data' : {
list
}
};
// console.log(postReq);
await axios(postReq)
.then((d) => {
// console.log('>> retrieved', d);
const data = JSON.parse(d.data);
const list = data.map((item) => {
return item.id;
});
const tMap = data.map((item) => {
return [item.id, item];
});
this.tweetList.set(list);
this.tweets.set(new Map(tMap));
});
},
getTweetByID(id) {
return get(this.tweets).get(id);
},
getTwitterFollowing() {
return get(this.twitterFollowing);
},
saveTwitterFollowing(newVals) {
this.twitterFollowing.set(newVals);
},
getBaseUrl() {
return baseUrl;
}
};
localforage.getItem('twitterFollowing').then((value) => {
if (value !== null) state.twitterFollowing.set(value);
else {
state.twitterFollowing.set(twitterAccounts);
localforage.setItem('twitterFollowing', twitterAccounts).catch((err) => {
// This code runs if there were any errors
console.error(err);
});
}
// console.log('twitterFollowing', get(state.twitterFollowing));
}).catch((err) => {
console.error(err);
});
localforage.getItem('favourites').then((value) => {
if (value !== null) state.favourites.set(value);
}).catch((err) => {
console.error(err);
});
localforage.getItem('routes').then((value) => {
if (value !== null) state.routes.set(value);
}).catch((err) => {
console.error(err);
});
localforage.getItem('routeIndex').then((value) => {
if (value !== null) state.routeIndex.set(value);
}).catch((err) => {
console.error(err);
});
state.favourites.subscribe((v) => {
console.log('>> Store.state.favourites', v);
});
state.routes.subscribe((v) => {
if (started)
// console.log('>> Store.state.routes', v);
localforage.setItem('routes', v).catch((err) => {
// This code runs if there were any errors
console.error(err);
});
});
state.routeIndex.subscribe((v) => {
if (started)
// console.log('>> Store.state.routeIndex', v);
localforage.setItem('routeIndex', v).catch((err) => {
// This code runs if there were any errors
console.error(err);
});
});
state.twitterFollowing.subscribe((v) => {
if (started)
// console.log('>> Store.state.twitterFollowing', v);
localforage.setItem('twitterFollowing', v).catch((err) => {
// This code runs if there were any errors
console.error(err);
});
const list = Object.entries(get(state.twitterFollowing)).filter((item) => {
if (item[1].follow) return item;
});
state.twitterFollowingList.set(list);
});
// started = true;
setTimeout(() => {
started = true;
}, 250);
export { state };

21
test.html Normal file
View File

@ -0,0 +1,21 @@
<!doctype html>
<html >
<head>
<meta charset="utf-8">
<title>amp-twitter - Example 1</title>
<script async src="https://cdn.ampproject.org/v0.js"></script>
<script custom-element="amp-twitter" src="https://cdn.ampproject.org/v0/amp-twitter-0.1.js" async></script>
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
</head>
<body>
<amp-twitter
width="375"
height="472"
layout="responsive"
data-tweetid="1255922296860090400"
>
</amp-twitter>
</body>
</html>