Prettier formatting

This commit is contained in:
Fred Boniface 2023-07-07 11:27:28 +01:00
parent 039b57efe7
commit 7dc24646b9
49 changed files with 2796 additions and 2419 deletions

View File

@ -1,11 +1,6 @@
module.exports = { module.exports = {
root: true, root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'], extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
overrides: [
{
"files": ["**/*.svelte"]
}
],
parserOptions: { parserOptions: {
sourceType: 'module', sourceType: 'module',
ecmaVersion: 2020, ecmaVersion: 2020,
@ -17,6 +12,6 @@ module.exports = {
node: true node: true
}, },
rules: { rules: {
indent: ['error', 2, {SwitchCase: 2}], indent: ['error', 2, { SwitchCase: 2 }]
} }
}; };

View File

@ -1,9 +1,11 @@
{ {
"useTabs": true, "useTabs": false,
"singleQuote": true, "tabWidth": 2,
"trailingComma": "none", "semi": true,
"printWidth": 100, "singleQuote": true,
"plugins": ["prettier-plugin-svelte"], "trailingComma": "none",
"pluginSearchDirs": ["."], "printWidth": 80,
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] "plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
} }

View File

@ -1,17 +1,17 @@
{ {
"extends": "./.svelte-kit/tsconfig.json", "extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true "strict": true
} }
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias and https://kit.svelte.dev/docs/configuration#files
// //
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in // from the referenced tsconfig.json - TypeScript does not merge them in
} }

View File

@ -1,29 +1,29 @@
{ {
"name": "owlboard-svelte", "name": "owlboard-svelte",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .", "lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ." "format": "prettier --plugin-search-dir . --write ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0", "@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-static": "^2.0.2", "@sveltejs/adapter-static": "^2.0.2",
"@sveltejs/kit": "^1.5.0", "@sveltejs/kit": "^1.5.0",
"eslint": "^8.28.0", "eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.26.0", "eslint-plugin-svelte": "^2.26.0",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1", "prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0", "svelte": "^3.54.0",
"svelte-check": "^3.0.1", "svelte-check": "^3.0.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
"vite": "^4.3.0" "vite": "^4.3.0"
}, },
"type": "module" "type": "module"
} }

View File

@ -1,72 +1,75 @@
/* FONTS */ /* FONTS */
@font-face { @font-face {
font-family: 'firamono'; font-family: 'firamono';
src: url('/font/firamono/firamono-regular.woff2') format('woff2'), src: url('/font/firamono/firamono-regular.woff2') format('woff2'),
url('/font/firamono/firamono-regular.woff') format('woff'), url('/font/firamono/firamono-regular.woff') format('woff'),
url('/font/firamono/firamono-regular.ttf') format('truetype'); url('/font/firamono/firamono-regular.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'firamono'; font-family: 'firamono';
src: url('/font/firamono/firamono-500.woff2') format('woff2'), src: url('/font/firamono/firamono-500.woff2') format('woff2'),
url('/font/firamono/firamono-500.woff') format('woff'), url('/font/firamono/firamono-500.woff') format('woff'),
url('/font/firamono/firamono-500.ttf') format('truetype'); url('/font/firamono/firamono-500.ttf') format('truetype');
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'urwgothic'; font-family: 'urwgothic';
src: url('/font/urwgothic/urwgothic.woff2') format('woff2'), src: url('/font/urwgothic/urwgothic.woff2') format('woff2'),
url('/font/urwgothic/urwgothic.woff') format('woff'), url('/font/urwgothic/urwgothic.woff') format('woff'),
url('/font/urwgothic/urwgothic.ttf') format('truetype'); url('/font/urwgothic/urwgothic.ttf') format('truetype');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@font-face { @font-face {
font-family: 'urwgothic'; font-family: 'urwgothic';
src: url('/font/urwgothic/urwgothicDemi.woff2') format('woff2'), src: url('/font/urwgothic/urwgothicDemi.woff2') format('woff2'),
url('/font/urwgothic/urwgothicDemi.woff') format('woff'), url('/font/urwgothic/urwgothicDemi.woff') format('woff'),
url('/font/urwgothic/urwgothicDemi.ttf') format('truetype'); url('/font/urwgothic/urwgothicDemi.ttf') format('truetype');
font-weight: 900; font-weight: 900;
font-style: normal; font-style: normal;
} }
/* COLOR VARS */ /* COLOR VARS */
:root { :root {
--main-bg-color: #404c55; --main-bg-color: #404c55;
--second-bg-color: #2b343c; /* Use as first arg in radial gradient */ --second-bg-color: #2b343c; /* Use as first arg in radial gradient */
--accent-color: #007979; --accent-color: #007979;
--overlay-color: #3c6f79de; --overlay-color: #3c6f79de;
--overlay-color-solid: #3c6f79; --overlay-color-solid: #3c6f79;
--main-text-color: #00b7b7; --main-text-color: #00b7b7;
--second-text-color: #0afdfd; --second-text-color: #0afdfd;
--note-text-color: #9de7ff; --note-text-color: #9de7ff;
--link-color: azure; --link-color: azure;
--box-border-color: ; --box-border-color: ;
--link-visited-color: azure; --link-visited-color: azure;
--main-alert-color: #ed6d00; --main-alert-color: #ed6d00;
--second-alert-color: #e77f00; /* Use as second arg in radial gradient */ --second-alert-color: #e77f00; /* Use as second arg in radial gradient */
--main-warning-color: orange; --main-warning-color: orange;
--board-name-color: #fcfc09; --board-name-color: #fcfc09;
} }
html{ html {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
body { body {
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
background-image: radial-gradient(var(--second-bg-color), var(--main-bg-color)); background-image: radial-gradient(
color: var(--second-text-color); var(--second-bg-color),
font-family: urwgothic, sans-serif; var(--main-bg-color)
text-align: center; );
padding-bottom: 60px; /*Footer height*/ color: var(--second-text-color);
} font-family: urwgothic, sans-serif;
a { text-align: center;
color: var(--link-color) padding-bottom: 60px; /*Footer height*/
} }
button:hover { a {
cursor: pointer; color: var(--link-color);
} }
button:hover {
cursor: pointer;
}

12
src/app.d.ts vendored
View File

@ -1,12 +1,12 @@
// See https://kit.svelte.dev/docs/types#app // See https://kit.svelte.dev/docs/types#app
// for information about these interfaces // for information about these interfaces
declare global { declare global {
namespace App { namespace App {
// interface Error {} // interface Error {}
// interface Locals {} // interface Locals {}
// interface PageData {} // interface PageData {}
// interface Platform {} // interface Platform {}
} }
} }
export {}; export {};

View File

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@ -1,10 +1,10 @@
<img src="/images/logo/wide_logo.svg" alt="Logo"> <img src="/images/logo/wide_logo.svg" alt="Logo" />
<style> <style>
img { img {
width: 50%; width: 50%;
max-width: 250px; max-width: 250px;
margin-top: 55px; margin-top: 55px;
margin-bottom: 55px; margin-bottom: 55px;
} }
</style> </style>

View File

@ -1,45 +1,54 @@
<script> <script>
import Island from "$lib/islands/island.svelte"; import Island from '$lib/islands/island.svelte';
export let variables = { export let variables = {
title: "Uninitialised", title: 'Uninitialised',
action: "/", action: '/',
placeholder: "Uninitialised", placeholder: 'Uninitialised',
queryName: "uninitiailsed" queryName: 'uninitiailsed'
}; };
</script> </script>
<Island {variables}> <Island {variables}>
<form action={variables.action}> <form action={variables.action}>
<input class="form-input" type="text" id="input-headcode" name={variables.queryName} placeholder={variables.placeholder} autocomplete="off"> <input
<br> class="form-input"
type="text"
id="input-headcode"
name={variables.queryName}
placeholder={variables.placeholder}
autocomplete="off"
/>
<br />
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</Island> </Island>
<style> <style>
.form-input { .form-input {
width: 75%; width: 75%;
height: 32px; height: 32px;
margin-top: 5px; margin-top: 5px;
margin-bottom: 5px; margin-bottom: 5px;
border-radius: 50px; border-radius: 50px;
border: none; border: none;
text-align: center; text-align: center;
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
text-transform: uppercase; 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
font-size: 15px; text-transform: uppercase;
} font-size: 15px;
button { }
width: 50%; button {
margin-bottom: 5px; width: 50%;
margin-top: 5px; margin-bottom: 5px;
border: none; margin-top: 5px;
border-radius: 20px; border: none;
padding: 5px; border-radius: 20px;
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; padding: 5px;
font-size: 16px; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
font-weight: 400; 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
background-color: var(--main-bg-color); font-size: 16px;
color: var(--link-color); font-weight: 400;
} background-color: var(--main-bg-color);
color: var(--link-color);
}
</style> </style>

View File

@ -1,20 +1,21 @@
<script> <script>
export let variables = {title:""} export let variables = { title: '' };
</script> </script>
<div> <div>
<span>{variables.title}</span> <span>{variables.title}</span>
<slot /> <slot />
</div> </div>
<style> <style>
span { span {
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
font-weight: 600; font-weight: 600;
font-size: 20px; font-size: 20px;
color: var(--main-text-color) color: var(--main-text-color);
} }
div { div {
width: 85%; width: 85%;
max-width: 400px; max-width: 400px;
margin: auto; margin: auto;
@ -22,5 +23,5 @@ div {
padding: 10px; padding: 10px;
background-color: var(--overlay-color); background-color: var(--overlay-color);
border-radius: 10px; border-radius: 10px;
} }
</style> </style>

View File

@ -1,22 +1,23 @@
<script> <script>
import { fade } from "svelte/transition"; import { fade } from 'svelte/transition';
export let variables = {title:""} export let variables = { title: '' };
</script> </script>
<div in:fade={{duration: 150}} out:fade={{duration: 150}}> <div in:fade={{ duration: 150 }} out:fade={{ duration: 150 }}>
<span>{variables.title}</span> <span>{variables.title}</span>
<slot /> <slot />
</div> </div>
<style> <style>
span { span {
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
color: var(--main-text-color); color: var(--main-text-color);
font-weight: 600; font-weight: 600;
font-size: 20px; font-size: 20px;
} }
div { div {
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
@ -32,5 +33,5 @@ div {
background-color: var(--overlay-color); background-color: var(--overlay-color);
border-radius: 10px; border-radius: 10px;
z-index: 1000; z-index: 1000;
} }
</style> </style>

View File

@ -1,57 +1,54 @@
<script> <script>
import Island from "$lib/islands/island.svelte"; import Island from '$lib/islands/island.svelte';
import { ql } from "$lib/stores/quick-links.js" import { ql } from '$lib/stores/quick-links.js';
export let variables = { export let variables = {
title: "Quick Links", title: 'Quick Links'
}; };
</script> </script>
<Island {variables}> <Island {variables}>
{#if $ql.length === 0}
{#if $ql.length === 0}
<p>Go to <a href="/more/settings">settings</a> to add your Quick Links</p> <p>Go to <a href="/more/settings">settings</a> to add your Quick Links</p>
{/if} {/if}
<div class="buttons"> <div class="buttons">
{#each $ql as link} {#each $ql as link}
{#if link.length === 3} {#if link.length === 3}
<a class="link" href="/ldb?station={link}"> <a class="link" href="/ldb?station={link}">
{link.toUpperCase()} {link.toUpperCase()}
</a> </a>
{:else if link.length === 4} {:else if link.length === 4}
<a class="link" href="/train?headcode={link}"> <a class="link" href="/train?headcode={link}">
{link.toUpperCase()} {link.toUpperCase()}
</a> </a>
{/if} {/if}
{/each} {/each}
</div> </div>
</Island> </Island>
<style> <style>
.buttons { .buttons {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 90%; width: 90%;
margin: auto; margin: auto;
padding-top: 5px; padding-top: 5px;
} }
.link { .link {
flex: 1; flex: 1;
width: 20%; width: 20%;
min-width: 50px; min-width: 50px;
margin: 5px; margin: 5px;
border: none; border: none;
border-radius: 20px; border-radius: 20px;
padding: 5px; padding: 5px;
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
font-size: 16px; 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
font-weight: 400; font-size: 16px;
text-decoration: none; font-weight: 400;
background-color: var(--main-bg-color); text-decoration: none;
color: var(--link-color); background-color: var(--main-bg-color);
} color: var(--link-color);
}
</style> </style>

View File

@ -1,116 +1,117 @@
<script> <script>
import Island from "$lib/islands/island.svelte"; import Island from '$lib/islands/island.svelte';
import { ql } from "$lib/stores/quick-links.js"; import { ql } from '$lib/stores/quick-links.js';
export let variables = { export let variables = {
title: "Quick Links", title: 'Quick Links'
}; };
let qlData =[] let qlData = [];
$: { $: {
qlData = $ql; qlData = $ql;
console.log(qlData); console.log(qlData);
} }
let saveButton = "Save" let saveButton = 'Save';
async function timeout(ms) { async function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
async function saveQl() { async function saveQl() {
// Fetch the content of all text entries within the island then run ql.set([ARRAY OF INPUT CONTENT]) // Fetch the content of all text entries within the island then run ql.set([ARRAY OF INPUT CONTENT])
const inputs = document.getElementsByClassName('qlInput'); const inputs = document.getElementsByClassName('qlInput');
let inputLinks = [] let inputLinks = [];
for (let item of inputs) { for (let item of inputs) {
let text = item?.value; let text = item?.value;
if (text !== '') { if (text !== '') {
inputLinks.push(text); inputLinks.push(text);
}
} }
console.log(inputLinks)
ql.set(inputLinks)
saveButton = "&#10004;"
await timeout(3000);
saveButton = "Saved"
} }
console.log(inputLinks);
ql.set(inputLinks);
saveButton = '&#10004;';
await timeout(3000);
saveButton = 'Saved';
}
function clearQl() { function clearQl() {
ql.set([]); ql.set([]);
saveButton = "Saved" saveButton = 'Saved';
} }
function addQlBox() { function addQlBox() {
saveButton = "Save" saveButton = 'Save';
const updatedQl = [...$ql, ""]; const updatedQl = [...$ql, ''];
$ql = updatedQl; $ql = updatedQl;
ql.set(updatedQl); ql.set(updatedQl);
} }
function handleClick(event) { function handleClick(event) {
// Handle the click event here // Handle the click event here
console.log("Island Clicked"); console.log('Island Clicked');
// You can access the `variables` passed to the Island component here if needed // You can access the `variables` passed to the Island component here if needed
} }
</script> </script>
<Island on:click={handleClick} {variables}>
{#if $ql.length === 0}
<p>Click the + button to add links</p>
{/if}
<div id="buttons" class="buttons">
<p>Quick links can be CRS Codes or Headcodes</p>
{#each qlData as link}
<input class="qlInput" type="text" value={link}>
{/each}
<button on:click={addQlBox} id="qlAdd">+</button>
</div>
<button on:click={saveQl}>{@html saveButton}</button>
<button on:click={clearQl}>Clear</button>
</Island>
<Island on:click={handleClick} {variables}>
{#if $ql.length === 0}
<p>Click the + button to add links</p>
{/if}
<div id="buttons" class="buttons">
<p>Quick links can be CRS Codes or Headcodes</p>
{#each qlData as link}
<input class="qlInput" type="text" value={link} />
{/each}
<button on:click={addQlBox} id="qlAdd">+</button>
</div>
<button on:click={saveQl}>{@html saveButton}</button>
<button on:click={clearQl}>Clear</button>
</Island>
<style> <style>
p { p {
margin-bottom: 0; margin-bottom: 0;
} }
#qlAdd { #qlAdd {
width: 40px; width: 40px;
} }
.buttons { .buttons {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 90%; width: 90%;
margin: auto; margin: auto;
padding-top: 5px; padding-top: 5px;
} }
input { input {
flex: 1; flex: 1;
width: 20%; width: 20%;
min-width: 50px; min-width: 50px;
margin: 5px; margin: 5px;
border: none; border: none;
border-radius: 20px; border-radius: 20px;
padding: 5px; padding: 5px;
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
font-size: 16px; 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
font-weight: 400; font-size: 16px;
text-decoration: none; font-weight: 400;
text-align: center; text-decoration: none;
text-transform: uppercase; text-align: center;
} text-transform: uppercase;
button { }
width: 30%; button {
margin-bottom: 5px; width: 30%;
margin-top: 10px; margin-bottom: 5px;
border: none; margin-top: 10px;
border-radius: 20px; border: none;
padding: 5px; border-radius: 20px;
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; padding: 5px;
font-size: 16px; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
font-weight: 400; 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
background-color: var(--main-bg-color); font-size: 16px;
color: var(--link-color); font-weight: 400;
} background-color: var(--main-bg-color);
color: var(--link-color);
}
</style> </style>

View File

@ -1,27 +1,24 @@
<script> <script>
import Island from "$lib/islands/island.svelte"; import Island from '$lib/islands/island.svelte';
export let resultObject = { export let resultObject = {
results: true, results: true,
title: "", title: '',
resultLines: [] resultLines: []
}; };
let variables = {
title: resultObject.title
}
let variables = {
title: resultObject.title
};
</script> </script>
<Island {variables}> <Island {variables}>
{#each resultObject.resultLines as line}
{#each resultObject.resultLines as line} <p>{line}</p>
<p>{line}</p> {/each}
{/each}
</Island> </Island>
<style> <style>
p { p {
color: white; color: white;
} }
</style> </style>

View File

@ -1,116 +1,132 @@
<script> <script>
import { fly } from "svelte/transition"; import { fly } from 'svelte/transition';
export let alerts = []; export let alerts = [];
$: uniqueAlerts = [...new Set(alerts)]; $: uniqueAlerts = [...new Set(alerts)];
let displayAlerts = false; let displayAlerts = false;
async function alertsToggle() { async function alertsToggle() {
displayAlerts = !displayAlerts displayAlerts = !displayAlerts;
} }
function numberAsWord(number) { function numberAsWord(number) {
const words = ['zero', 'one', 'two','three','four','five','six','seven','eight']; const words = [
let word = words[number]; 'zero',
if (word) { 'one',
return word; 'two',
} 'three',
return number; 'four',
'five',
'six',
'seven',
'eight'
];
let word = words[number];
if (word) {
return word;
} }
return number;
}
</script> </script>
<div id="block"><!--Prevent content slipping underneath the bar--></div> <div id="block"><!--Prevent content slipping underneath the bar--></div>
<div id="bar" on:click={alertsToggle} on:keypress={alertsToggle}> <div id="bar" on:click={alertsToggle} on:keypress={alertsToggle}>
<img src="/images/navigation/alert.svg" alt=""> <img src="/images/navigation/alert.svg" alt="" />
{#if uniqueAlerts.length == 1} {#if uniqueAlerts.length == 1}
<p id="bartext">There is one active alert</p> <p id="bartext">There is one active alert</p>
{:else if uniqueAlerts.length > 1} {:else if uniqueAlerts.length > 1}
<p id="bartext">There are {numberAsWord(uniqueAlerts.length)} active alerts</p> <p id="bartext">
{:else} There are {numberAsWord(uniqueAlerts.length)} active alerts
<p id="bartext">There are no active alerts</p> </p>
{/if} {:else}
<p id="arrow" class:displayAlerts>V</p> <p id="bartext">There are no active alerts</p>
{/if}
<p id="arrow" class:displayAlerts>V</p>
</div> </div>
{#if displayAlerts} {#if displayAlerts}
<div id="alerts" in:fly={{ y:-200, duration: 500}} out:fly={{ y: -200, duration: 800 }}> <div
{#each uniqueAlerts as msg} id="alerts"
<p class="alert">{@html msg}</p> in:fly={{ y: -200, duration: 500 }}
{/each} out:fly={{ y: -200, duration: 800 }}
</div> >
{#each uniqueAlerts as msg}
<p class="alert">{@html msg}</p>
{/each}
</div>
{/if} {/if}
<style> <style>
#block { #block {
height: 40px; height: 40px;
} }
#bar { #bar {
width: 100%; width: 100%;
height: 40px; height: 40px;
background-color: var(--main-alert-color); background-color: var(--main-alert-color);
opacity: 1; opacity: 1;
position: fixed; position: fixed;
width: 100%; width: 100%;
top: 50px; top: 50px;
left: 0px; left: 0px;
z-index: 10; z-index: 10;
} }
img { img {
height: 25px; height: 25px;
width: 25px; width: 25px;
position: absolute; position: absolute;
left: 8px; left: 8px;
top: 8px; top: 8px;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
#bartext { #bartext {
color: white; color: white;
margin: auto; margin: auto;
font-weight: 600; font-weight: 600;
margin-top: 8px; margin-top: 8px;
margin-bottom: 0px; margin-bottom: 0px;
padding: 0px; padding: 0px;
} }
#arrow{ #arrow {
color: white; color: white;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
font-weight: 900; font-weight: 900;
position: absolute; position: absolute;
margin-top: 0; margin-top: 0;
right: 15px; right: 15px;
top: 11px; top: 11px;
border: none; border: none;
background-color: transparent; background-color: transparent;
transition-duration: 500ms; transition-duration: 500ms;
transition-delay: 00ms; transition-delay: 00ms;
} }
#arrow:focus { #arrow:focus {
background-color: transparent; background-color: transparent;
} }
#alerts { #alerts {
position: fixed; position: fixed;
background-color: var(--main-alert-color); background-color: var(--main-alert-color);
opacity: 0.9; opacity: 0.9;
width: 100%; width: 100%;
max-height: 80vh; max-height: 80vh;
overflow-y: auto; overflow-y: auto;
overflow-x: clip; overflow-x: clip;
left: 0; left: 0;
top: 89px; top: 89px;
} }
.alert { .alert {
color: white; color: white;
text-align: center; text-align: center;
width: 80%; width: 80%;
margin:auto; margin: auto;
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
font-weight: 600; font-weight: 600;
} }
.displayAlerts { .displayAlerts {
transition-duration: 500ms; transition-duration: 500ms;
transition-delay: 400ms; transition-delay: 400ms;
transform: rotate(180deg); transform: rotate(180deg);
} }
</style> </style>

View File

@ -1,10 +1,10 @@
<script> <script>
export let station = ""; export let station = '';
export let title = "Loading..."; export let title = 'Loading...';
import { onMount } from 'svelte' import { onMount } from 'svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import OverlayIsland from '$lib/islands/overlay-island.svelte'; import OverlayIsland from '$lib/islands/overlay-island.svelte';
import AlertBar from './alert-bar.svelte'; import AlertBar from './alert-bar.svelte';
let requestedStation; let requestedStation;
$: requestedStation = station; $: requestedStation = station;
@ -20,96 +20,98 @@
let serviceDetail; let serviceDetail;
$: { $: {
if (jsonData === null && requestedStation) { if (jsonData === null && requestedStation) {
fetchData(); fetchData();
}
if (jsonData?.GetStationBoardResult?.generatedAt) {
dataAge = new Date(jsonData.GetStationBoardResult.generatedAt);
}
if (jsonData?.GetStationBoardResult?.trainServices?.service) {
services = jsonData.GetStationBoardResult.trainServices.service;
} else {
services = [];
}
if (jsonData?.GetStationBoardResult?.busServices?.service) {
busServices = jsonData.GetStationBoardResult.busServices.service;
}
if (jsonData?.GetStationBoardResult?.ferryServices?.service) {
ferryServices = jsonData.GetStationBoardResult.ferryServices.service;
}
if (jsonData?.GetStationBoardResult?.locationName) {
title = jsonData.GetStationBoardResult.locationName
} else {
title = requestedStation.toUpperCase()
}
} }
if (jsonData?.GetStationBoardResult?.generatedAt) {
dataAge = new Date(jsonData.GetStationBoardResult.generatedAt);
}
if (jsonData?.GetStationBoardResult?.trainServices?.service) {
services = jsonData.GetStationBoardResult.trainServices.service;
} else {
services = [];
}
if (jsonData?.GetStationBoardResult?.busServices?.service) {
busServices = jsonData.GetStationBoardResult.busServices.service;
}
if (jsonData?.GetStationBoardResult?.ferryServices?.service) {
ferryServices = jsonData.GetStationBoardResult.ferryServices.service;
}
if (jsonData?.GetStationBoardResult?.locationName) {
title = jsonData.GetStationBoardResult.locationName;
} else {
title = requestedStation.toUpperCase();
}
}
async function fetchData() { async function fetchData() {
dataExists = true; dataExists = true;
isLoading = true; // Set loading state isLoading = true; // Set loading state
try { try {
console.log(`Requested Station: ${requestedStation}`); console.log(`Requested Station: ${requestedStation}`);
const data = await fetch(`https://owlboard.info/api/v1/ldb/${requestedStation}`); const data = await fetch(
`https://owlboard.info/api/v1/ldb/${requestedStation}`
);
jsonData = await data.json(); jsonData = await data.json();
} catch (error) { } catch (error) {
console.error("Error fetching data:", error); console.error('Error fetching data:', error);
dataExists = false; dataExists = false;
title = "Not Found"; title = 'Not Found';
} finally { } finally {
isLoading = false; // Clear loading state isLoading = false; // Clear loading state
} }
prepareNrcc() prepareNrcc();
} }
function parseTime(string){ function parseTime(string) {
let output let output;
let change let change;
switch (string) { switch (string) {
case 'Delayed': case 'Delayed':
output = 'LATE' output = 'LATE';
change = "changed" change = 'changed';
break break;
case 'Cancelled': case 'Cancelled':
output = 'CANC' output = 'CANC';
change = "cancelled" change = 'cancelled';
break break;
case 'On Time': case 'On Time':
case 'On time': case 'On time':
output = 'RT' output = 'RT';
change = "" change = '';
break break;
case '': case '':
output = '-' output = '-';
change = "" change = '';
break break;
case undefined: case undefined:
output = '-' output = '-';
change = "" change = '';
break break;
case 'No report': case 'No report':
output = '-' output = '-';
change = "" change = '';
break break;
case 'undefined': case 'undefined':
output = false output = false;
change = "" change = '';
break break;
default: default:
output = string output = string;
change = "changed" change = 'changed';
} }
return {data: output, changed: change} return { data: output, changed: change };
} }
async function loadService(sid) { async function loadService(sid) {
for (const service of services) { for (const service of services) {
if (service.serviceID == sid) { if (service.serviceID == sid) {
serviceDetail = service serviceDetail = service;
} }
} }
} }
@ -117,7 +119,7 @@
async function loadBusService(sid) { async function loadBusService(sid) {
for (const service of busServices) { for (const service of busServices) {
if (service.serviceID == sid) { if (service.serviceID == sid) {
serviceDetail = service serviceDetail = service;
} }
} }
} }
@ -130,7 +132,7 @@
if (jsonData?.GetStationBoardResult?.nrccMessages?.message) { if (jsonData?.GetStationBoardResult?.nrccMessages?.message) {
const nrcc = jsonData.GetStationBoardResult.nrccMessages.message; const nrcc = jsonData.GetStationBoardResult.nrccMessages.message;
if (Array.isArray(nrcc)) { if (Array.isArray(nrcc)) {
alerts = nrcc alerts = nrcc;
return; return;
} }
alerts.push(nrcc); alerts.push(nrcc);
@ -146,53 +148,65 @@
</script> </script>
{#if alerts.length} {#if alerts.length}
<AlertBar {alerts}/> <AlertBar {alerts} />
{/if} {/if}
{#if isLoading} {#if isLoading}
<Loading /> <Loading />
{:else} {:else if dataAge}
{#if dataAge} <p id="timestamp">Updated: {dataAge.toLocaleTimeString()}</p>
<p id="timestamp">Updated: {dataAge.toLocaleTimeString()}</p> {#if services.length}
{#if services.length} <table class="ldbTable">
<table class="ldbTable"> <tr>
<th class="from">From</th>
<th class="to">To</th>
<th class="plat">Plat.</th>
<th class="time">Sch Arr.</th>
<th class="time">Exp Arr.</th>
<th class="time">Sch Dep.</th>
<th class="time">Exp Dep.</th>
</tr>
{#each services as service}
<tr> <tr>
<th class="from">From</th> <td
<th class="to">To</th> class="origdest from"
<th class="plat">Plat.</th> on:click={loadService(service.serviceID)}
<th class="time">Sch Arr.</th> on:keypress={loadService(service.serviceID)}
<th class="time">Exp Arr.</th> >
<th class="time">Sch Dep.</th> {#if Array.isArray(service.origin?.location)}
<th class="time">Exp Dep.</th> {service.origin.location[0]['locationName'] +
' & ' +
service.origin.location[1]['locationName']}
{:else}
{service.origin?.location?.locationName || ''}
{/if}
</td>
<td
class="origdest to"
on:click={loadService(service.serviceID)}
on:keypress={loadService(service.serviceID)}
>
{#if Array.isArray(service.destination?.location)}
{service.destination.location[0]['locationName'] +
' & ' +
service.destination.location[0]['locationName']}
{:else}
{service.destination?.location?.locationName || ''}
{/if}
</td>
<td class="plat">{service.platform || '-'}</td>
<td class="time">{parseTime(service.sta).data}</td>
<td class="time {parseTime(service.eta).changed}"
>{parseTime(service.eta).data}</td
>
<td class="time">{parseTime(service.std).data}</td>
<td class="time {parseTime(service.etd).changed}"
>{parseTime(service.etd).data}</td
>
</tr> </tr>
{#each services as service}
<tr>
<td class="origdest from" on:click={loadService(service.serviceID)} on:keypress={loadService(service.serviceID)}>
{#if Array.isArray(service.origin?.location)}
{service.origin.location[0]['locationName'] +
" & " +
service.origin.location[1]['locationName']}
{:else}
{service.origin?.location?.locationName || ''}
{/if}
</td>
<td class="origdest to" on:click={loadService(service.serviceID)} on:keypress={loadService(service.serviceID)}>
{#if Array.isArray(service.destination?.location)}
{service.destination.location[0]['locationName'] +
" & " +
service.destination.location[0]['locationName']}
{:else}
{service.destination?.location?.locationName || ''}
{/if}
</td>
<td class="plat">{service.platform || '-'}</td>
<td class="time">{parseTime(service.sta).data}</td>
<td class="time {parseTime(service.eta).changed}">{parseTime(service.eta).data}</td>
<td class="time">{parseTime(service.std).data}</td>
<td class="time {parseTime(service.etd).changed}">{parseTime(service.etd).data}</td>
</tr>
<tr><td colspan="7"> <tr
><td colspan="7">
<p class="service-detail"> <p class="service-detail">
A {service.operator || 'Unknown'} service A {service.operator || 'Unknown'} service
{#if service['length']} {#if service['length']}
@ -205,36 +219,56 @@
{#if service.cancelReason} {#if service.cancelReason}
<p class="service-detail">{service.cancelReason}</p> <p class="service-detail">{service.cancelReason}</p>
{/if} {/if}
</td></tr> </td></tr
{/each} >
</table> {/each}
{:else} </table>
<p class="table-head-text">No Scheduled Train Services</p> {:else}
{/if} <p class="table-head-text">No Scheduled Train Services</p>
{#if busServices.length} {/if}
<br> {#if busServices.length}
<img class="transport-mode" src="/images/transport-modes/bus.svg" alt="Bus services"><br> <br />
<span class="table-head-text">Bus Services</span> <img
<table class="ldbTable"> class="transport-mode"
src="/images/transport-modes/bus.svg"
alt="Bus services"
/><br />
<span class="table-head-text">Bus Services</span>
<table class="ldbTable">
<tr>
<th class="from">From</th>
<th class="to">To</th>
<th class="time">Sch Arr.</th>
<th class="time">Exp Arr.</th>
<th class="time">Sch Dep.</th>
<th class="time">Exp Dep.</th>
</tr>
{#each busServices as service}
<tr> <tr>
<th class="from">From</th> <td
<th class="to">To</th> class="origdest from"
<th class="time">Sch Arr.</th> on:click={loadBusService(service.serviceID)}
<th class="time">Exp Arr.</th> on:keypress={loadBusService(service.serviceID)}
<th class="time">Sch Dep.</th> >{service.origin?.location?.locationName || ''}</td
<th class="time">Exp Dep.</th> >
<td
class="origdest to"
on:click={loadBusService(service.serviceID)}
on:keypress={loadBusService(service.serviceID)}
>{service.destination?.location?.locationName || ''}</td
>
<td class="time">{parseTime(service.sta).data}</td>
<td class="time {parseTime(service.eta).changed}"
>{parseTime(service.eta).data}</td
>
<td class="time">{parseTime(service.std).data}</td>
<td class="time {parseTime(service.etd).changed}"
>{parseTime(service.etd).data}</td
>
</tr> </tr>
{#each busServices as service}
<tr>
<td class="origdest from" on:click={loadBusService(service.serviceID)} on:keypress={loadBusService(service.serviceID)}>{service.origin?.location?.locationName || ''}</td>
<td class="origdest to" on:click={loadBusService(service.serviceID)} on:keypress={loadBusService(service.serviceID)}>{service.destination?.location?.locationName || ''}</td>
<td class="time">{parseTime(service.sta).data}</td>
<td class="time {parseTime(service.eta).changed}">{parseTime(service.eta).data}</td>
<td class="time">{parseTime(service.std).data}</td>
<td class="time {parseTime(service.etd).changed}">{parseTime(service.etd).data}</td>
</tr>
<tr><td colspan="7"> <tr
><td colspan="7">
<p class="service-detail"> <p class="service-detail">
A {service.operator || 'Unknown'} service A {service.operator || 'Unknown'} service
</p> </p>
@ -244,47 +278,61 @@
{#if service.cancelReason} {#if service.cancelReason}
<p class="service-detail">{service.cancelReason}</p> <p class="service-detail">{service.cancelReason}</p>
{/if} {/if}
</td></tr> </td></tr
{/each} >
</table> {/each}
{/if} </table>
{#if ferryServices.length} {/if}
<br> {#if ferryServices.length}
<img class="transport-mode" src="/images/transport-modes/ferry.svg" alt="Bus services"><br> <br />
<span class="table-head-text">Ferry Services</span> <img
<table class="ldbTable"> class="transport-mode"
src="/images/transport-modes/ferry.svg"
alt="Bus services"
/><br />
<span class="table-head-text">Ferry Services</span>
<table class="ldbTable">
<tr>
<th class="from">From</th>
<th class="to">To</th>
<th class="time">Sch Arr.</th>
<th class="time">Exp Arr.</th>
<th class="time">Sch Dep.</th>
<th class="time">Exp Dep.</th>
</tr>
{#each ferryServices as service}
<tr> <tr>
<th class="from">From</th> <td class="origdest from"
<th class="to">To</th> >{service.origin?.location?.locationName || ''}</td
<th class="time">Sch Arr.</th> >
<th class="time">Exp Arr.</th> <td class="origdest to"
<th class="time">Sch Dep.</th> >{service.destination?.location?.locationName || ''}</td
<th class="time">Exp Dep.</th> >
<td class="time">{parseTime(service.sta).data}</td>
<td class="time {parseTime(service.eta).changed}"
>{parseTime(service.eta).data}</td
>
<td class="time">{parseTime(service.std).data}</td>
<td class="time {parseTime(service.etd).changed}"
>{parseTime(service.etd).data}</td
>
</tr> </tr>
{#each ferryServices as service}
<tr>
<td class="origdest from">{service.origin?.location?.locationName || ''}</td>
<td class="origdest to">{service.destination?.location?.locationName || ''}</td>
<td class="time">{parseTime(service.sta).data}</td>
<td class="time {parseTime(service.eta).changed}">{parseTime(service.eta).data}</td>
<td class="time">{parseTime(service.std).data}</td>
<td class="time {parseTime(service.etd).changed}">{parseTime(service.etd).data}</td>
</tr>
<tr><td colspan="7"> <tr
><td colspan="7">
{#if service.delayReason} {#if service.delayReason}
<p class="service-detail">{service.delayReason}</p> <p class="service-detail">{service.delayReason}</p>
{/if} {/if}
{#if service.cancelReason} {#if service.cancelReason}
<p class="service-detail">{service.cancelReason}</p> <p class="service-detail">{service.cancelReason}</p>
{/if} {/if}
</td></tr> </td></tr
{/each} >
</table> {/each}
{/if} </table>
{:else}
<p>Unable to find this station</p>
{/if} {/if}
{:else}
<p>Unable to find this station</p>
{/if} {/if}
{#if serviceDetail} {#if serviceDetail}
@ -304,21 +352,47 @@
<tr> <tr>
<td>{prevPoint.locationName}</td> <td>{prevPoint.locationName}</td>
<td>{prevPoint.st}</td> <td>{prevPoint.st}</td>
<td class="time {parseTime(prevPoint.at || prevPoint.et).changed}">{parseTime(prevPoint.at || prevPoint.et).data}</td> <td
class="time {parseTime(prevPoint.at || prevPoint.et).changed}"
>{parseTime(prevPoint.at || prevPoint.et).data}</td
>
</tr> </tr>
{/each} {/each}
{:else} {:else}
<tr> <tr>
<td>{serviceDetail.previousCallingPoints.callingPointList.callingPoint.locationName}</td> <td
<td>{serviceDetail.previousCallingPoints.callingPointList.callingPoint.st}</td> >{serviceDetail.previousCallingPoints.callingPointList
<td class="time {parseTime(serviceDetail.previousCallingPoints.callingPointList.callingPoint.at || serviceDetail.previousCallingPoints.callingPointList.callingPoint.et).changed}">{parseTime(serviceDetail.previousCallingPoints.callingPointList.callingPoint.at || serviceDetail.previousCallingPoints.callingPointList.callingPoint.et).data}</td> .callingPoint.locationName}</td
>
<td
>{serviceDetail.previousCallingPoints.callingPointList
.callingPoint.st}</td
>
<td
class="time {parseTime(
serviceDetail.previousCallingPoints.callingPointList
.callingPoint.at ||
serviceDetail.previousCallingPoints.callingPointList
.callingPoint.et
).changed}"
>{parseTime(
serviceDetail.previousCallingPoints.callingPointList
.callingPoint.at ||
serviceDetail.previousCallingPoints.callingPointList
.callingPoint.et
).data}</td
>
</tr> </tr>
{/if} {/if}
{/if} {/if}
<tr class="thisStop"> <tr class="thisStop">
<td>{title}</td> <td>{title}</td>
<td>{serviceDetail.std || serviceDetail.sta}</td> <td>{serviceDetail.std || serviceDetail.sta}</td>
<td class="time {parseTime(serviceDetail.etd || serviceDetail.eta).changed}">{parseTime(serviceDetail.etd || serviceDetail.eta).data}</td> <td
class="time {parseTime(serviceDetail.etd || serviceDetail.eta)
.changed}"
>{parseTime(serviceDetail.etd || serviceDetail.eta).data}</td
>
</tr> </tr>
{#if serviceDetail?.subsequentCallingPoints?.callingPointList?.callingPoint} {#if serviceDetail?.subsequentCallingPoints?.callingPointList?.callingPoint}
{#if Array.isArray(serviceDetail?.subsequentCallingPoints?.callingPointList?.callingPoint)} {#if Array.isArray(serviceDetail?.subsequentCallingPoints?.callingPointList?.callingPoint)}
@ -326,14 +400,31 @@
<tr> <tr>
<td>{nextPoint.locationName}</td> <td>{nextPoint.locationName}</td>
<td>{nextPoint.st}</td> <td>{nextPoint.st}</td>
<td class="time {parseTime(nextPoint.et).changed}">{parseTime(nextPoint.et).data}</td> <td class="time {parseTime(nextPoint.et).changed}"
>{parseTime(nextPoint.et).data}</td
>
</tr> </tr>
{/each} {/each}
{:else} {:else}
<tr class="detailRow"> <tr class="detailRow">
<td>{serviceDetail.subsequentCallingPoints.callingPointList.callingPoint.locationName}</td> <td
<td>{serviceDetail.subsequentCallingPoints.callingPointList.callingPoint.st}</td> >{serviceDetail.subsequentCallingPoints.callingPointList
<td class="time {parseTime(serviceDetail.subsequentCallingPoints.callingPointList.callingPoint.et).changed}">{parseTime(serviceDetail.subsequentCallingPoints.callingPointList.callingPoint.et).data}</td> .callingPoint.locationName}</td
>
<td
>{serviceDetail.subsequentCallingPoints.callingPointList
.callingPoint.st}</td
>
<td
class="time {parseTime(
serviceDetail.subsequentCallingPoints.callingPointList
.callingPoint.et
).changed}"
>{parseTime(
serviceDetail.subsequentCallingPoints.callingPointList
.callingPoint.et
).data}</td
>
</tr> </tr>
{/if} {/if}
{/if} {/if}
@ -371,21 +462,40 @@
color: white; color: white;
font-size: 14px; font-size: 14px;
} }
@media (min-width: 800px) { @media (min-width: 800px) {
table {font-size: 15px; max-width: 850px;} table {
.service-detail {font-size: 14px;} font-size: 15px;
.transport-mode {width: 50px;} max-width: 850px;
#timestamp {font-size: 16px;} }
} .service-detail {
@media (min-width: 1000px) { font-size: 14px;
table {font-size: 17px;} }
.service-detail{font-size: 16px;} .transport-mode {
.table-head-text {font-size: 16px;} width: 50px;
} }
@media (min-width: 1600px) { #timestamp {
table {font-size: 19px;} font-size: 16px;
.service-detail {font-size: 18px;} }
} }
@media (min-width: 1000px) {
table {
font-size: 17px;
}
.service-detail {
font-size: 16px;
}
.table-head-text {
font-size: 16px;
}
}
@media (min-width: 1600px) {
table {
font-size: 19px;
}
.service-detail {
font-size: 18px;
}
}
.origdest { .origdest {
color: yellow; color: yellow;
} }
@ -398,58 +508,58 @@
text-align: left; text-align: left;
} }
.plat { .plat {
width: 10% width: 10%;
} }
.time { .time {
width: 10%; width: 10%;
} }
.changed{ .changed {
animation: pulse-change 1.5s linear infinite; animation: pulse-change 1.5s linear infinite;
} }
.cancelled { .cancelled {
animation: pulse-cancel 1.5s linear infinite; animation: pulse-cancel 1.5s linear infinite;
} }
@keyframes pulse-change { @keyframes pulse-change {
50% { 50% {
color: var(--main-warning-color); color: var(--main-warning-color);
} }
} }
@keyframes pulse-cancel { @keyframes pulse-cancel {
50% { 50% {
color: var(--main-alert-color); color: var(--main-alert-color);
} }
} }
#detailBox { #detailBox {
width: 100%; width: 100%;
} }
h6 { h6 {
position: absolute; position: absolute;
top: -25px; top: -25px;
left: 20px; left: 20px;
font-size: 18px; font-size: 18px;
} }
#closeService { #closeService {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
border: none; border: none;
border-radius: 60px; border-radius: 60px;
width: 35px; width: 35px;
height: 35px; height: 35px;
background-color: var(--main-bg-color); background-color: var(--main-bg-color);
color: white; color: white;
font-weight: 700; font-weight: 700;
font-size: 16px; font-size: 16px;
} }
#detailTable { #detailTable {
margin-top: 40px; margin-top: 40px;
color: white; color: white;
font-size: 15px; font-size: 15px;
} }
.thisStop { .thisStop {
color: yellow; color: yellow;
} }
</style> </style>

View File

@ -1,290 +1,327 @@
<script> <script>
export let station = ""; export let station = '';
export let title = "Loading..."; export let title = 'Loading...';
import { onMount } from 'svelte' import { onMount } from 'svelte';
import AlertBar from './alert-bar.svelte'; import AlertBar from './alert-bar.svelte';
import StaffTrainDetail from '$lib/ldb/staff-train-detail.svelte'; import StaffTrainDetail from '$lib/ldb/staff-train-detail.svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import Nav from '$lib/navigation/nav.svelte'; import Nav from '$lib/navigation/nav.svelte';
import { uuid } from '$lib/stores/uuid'; import { uuid } from '$lib/stores/uuid';
let requestedStation; let requestedStation;
$: requestedStation = station; $: requestedStation = station;
let jsonData = {}; let jsonData = {};
let services = []; let services = [];
let dataAge = null; let dataAge = null;
let isLoading = true; let isLoading = true;
let alerts = []; let alerts = [];
$: { $: {
if (jsonData?.GetBoardResult?.generatedAt) { if (jsonData?.GetBoardResult?.generatedAt) {
dataAge = new Date(jsonData.GetBoardResult.generatedAt); dataAge = new Date(jsonData.GetBoardResult.generatedAt);
}
if (jsonData?.GetBoardResult?.trainServices?.service) {
services = jsonData.GetBoardResult.trainServices.service;
} else {
services = [];
}
if (jsonData?.GetBoardResult?.locationName) {
title = jsonData.GetBoardResult.locationName
} else {
title = "Loading Board"
}
if (jsonData?.GetBoardResult?.nrccMessages) {
alerts = processNrcc(jsonData.GetBoardResult?.nrccMessages?.message)
}
} }
async function fetchData() { if (jsonData?.GetBoardResult?.trainServices?.service) {
isLoading = true; // Set loading state services = jsonData.GetBoardResult.trainServices.service;
try { } else {
console.log(`Requested Station: ${requestedStation}`); services = [];
const url = `https://owlboard.info/api/v2/live/station/${requestedStation}/staff`; }
const opt = {
method: "GET", if (jsonData?.GetBoardResult?.locationName) {
headers: { title = jsonData.GetBoardResult.locationName;
"uuid": $uuid } else {
} title = 'Loading Board';
}
if (jsonData?.GetBoardResult?.nrccMessages) {
alerts = processNrcc(jsonData.GetBoardResult?.nrccMessages?.message);
}
}
async function fetchData() {
isLoading = true; // Set loading state
try {
console.log(`Requested Station: ${requestedStation}`);
const url = `https://owlboard.info/api/v2/live/station/${requestedStation}/staff`;
const opt = {
method: 'GET',
headers: {
uuid: $uuid
} }
const data = await fetch(url, opt); };
jsonData = await data.json(); const data = await fetch(url, opt);
} catch (error) { jsonData = await data.json();
console.error("Error fetching data:", error); } catch (error) {
} finally { console.error('Error fetching data:', error);
isLoading = false; // Clear loading state } finally {
} isLoading = false; // Clear loading state
} }
async function getReasonCodeData(code) {
const url = `https://owlboard.info/api/v2/ref/reasonCode/${code}`
const res = await fetch(url);
const json = await res.json();
return json
}
async function generateServiceData(service) {
const timeDetails = await parseTimes(service);
let serviceData = {
from: await parseLocation(service.origin),
to: await parseLocation(service.destination),
length: await getTrainLength(service),
platform: await parsePlatform(service?.platform || "undefined"),
platformHidden: service?.platformIsHidden === "true",
schArr: timeDetails.schArr,
expArr: timeDetails.expArr,
schDep: timeDetails.schDep,
expDep: timeDetails.expDep,
isEarlyArr: timeDetails.earArr,
isLateArr: timeDetails.delArr,
isEarlyDep: timeDetails.earDep,
isLateDep: timeDetails.delDep,
isCancelledDep: false,
isCancelled: Boolean(service?.isCancelled),
isDelayed: service?.arrivalType === "Delayed",
isArrDelayed: service?.arrivalType === "Delayed",
isDepDelayed: service?.departureType === "Delayed",
isEarly: false,
isNonPublic: false
}
return serviceData;
}
async function getTrainLength(service) {
if (service?.length) {
return parseInt(service?.length);
} else if (service?.formation?.coaches) {
return service.formation.coaches.coach.length
}
return null;
}
async function parseLocation(location) {
if (!Array.isArray(location.location)) {
//console.log(location.location?.tiploc)
return location.location?.tiploc
}
let locations = [];
for (const singleLocation of location?.location) {
locations.push(singleLocation?.tiploc)
}
return locations.join(' & ');
}
async function parsePlatform(platform) {
if (!platform) {
return '-'
}
if (platform === "TBC" || platform == "undefined") {
return '-'
}
return {
number: platform
}
}
function parseTimes(service){
let schArr = new Date(service?.sta);
let expArr = new Date(service?.eta || service?.ata);
let schDep = new Date(service?.std);
let expDep = new Date(service?.etd || service?.atd);
let isEarlyArr = false, isDelayedArr = false, isArr = false
let isEarlyDep = false, isDelayedDep = false, isDep = false
const timeDifferenceThreshold = 60 * 1000; // 60 seconds in milliseconds
if (expArr - schArr < -timeDifferenceThreshold) {
isEarlyArr = true;
isArr = true;
} else if (expArr - schArr > timeDifferenceThreshold) {
isDelayedArr = true;
isArr = true;
}
if (expDep - schDep < -timeDifferenceThreshold) {
isEarlyDep = true;
isDep = true;
} else if (expDep - schDep > timeDifferenceThreshold) {
isDelayedDep = true;
isDep = true;
}
let parsedExpArr;
if (expArr instanceof Date && !isNaN(expArr)) {
if (!isEarlyArr && !isDelayedArr) {
parsedExpArr = 'RT';
} else {
parsedExpArr = parseIndividualTime(expArr);
} }
} else {
parsedExpArr = '-';
}
let parsedExpDep; async function getReasonCodeData(code) {
if (expDep instanceof Date && !isNaN(expDep)) { const url = `https://owlboard.info/api/v2/ref/reasonCode/${code}`;
if (!isEarlyDep && !isDelayedDep) { const res = await fetch(url);
parsedExpDep = 'RT'; const json = await res.json();
} else { return json;
parsedExpDep = parseIndividualTime(expDep);
} }
} else {
parsedExpDep = '-'; async function generateServiceData(service) {
} const timeDetails = await parseTimes(service);
return { let serviceData = {
schArr: parseIndividualTime(schArr), from: await parseLocation(service.origin),
expArr: parsedExpArr, to: await parseLocation(service.destination),
schDep: parseIndividualTime(schDep), length: await getTrainLength(service),
expDep: parsedExpDep, platform: await parsePlatform(service?.platform || 'undefined'),
earArr: isEarlyArr, platformHidden: service?.platformIsHidden === 'true',
delArr: isDelayedArr, schArr: timeDetails.schArr,
earDep: isEarlyDep, expArr: timeDetails.expArr,
delDep: isDelayedDep schDep: timeDetails.schDep,
} expDep: timeDetails.expDep,
isEarlyArr: timeDetails.earArr,
isLateArr: timeDetails.delArr,
isEarlyDep: timeDetails.earDep,
isLateDep: timeDetails.delDep,
isCancelledDep: false,
isCancelled: Boolean(service?.isCancelled),
isDelayed: service?.arrivalType === 'Delayed',
isArrDelayed: service?.arrivalType === 'Delayed',
isDepDelayed: service?.departureType === 'Delayed',
isEarly: false,
isNonPublic: false
};
return serviceData;
}
async function getTrainLength(service) {
if (service?.length) {
return parseInt(service?.length);
} else if (service?.formation?.coaches) {
return service.formation.coaches.coach.length;
}
return null;
}
async function parseLocation(location) {
if (!Array.isArray(location.location)) {
//console.log(location.location?.tiploc)
return location.location?.tiploc;
}
let locations = [];
for (const singleLocation of location?.location) {
locations.push(singleLocation?.tiploc);
}
return locations.join(' & ');
}
async function parsePlatform(platform) {
if (!platform) {
return '-';
}
if (platform === 'TBC' || platform == 'undefined') {
return '-';
}
return {
number: platform
};
}
function parseTimes(service) {
let schArr = new Date(service?.sta);
let expArr = new Date(service?.eta || service?.ata);
let schDep = new Date(service?.std);
let expDep = new Date(service?.etd || service?.atd);
let isEarlyArr = false,
isDelayedArr = false,
isArr = false;
let isEarlyDep = false,
isDelayedDep = false,
isDep = false;
const timeDifferenceThreshold = 60 * 1000; // 60 seconds in milliseconds
if (expArr - schArr < -timeDifferenceThreshold) {
isEarlyArr = true;
isArr = true;
} else if (expArr - schArr > timeDifferenceThreshold) {
isDelayedArr = true;
isArr = true;
} }
function parseIndividualTime(input) { if (expDep - schDep < -timeDifferenceThreshold) {
const dt = new Date(input); isEarlyDep = true;
const output = dt.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) isDep = true;
if (output !== "Invalid Date") { } else if (expDep - schDep > timeDifferenceThreshold) {
return output isDelayedDep = true;
} isDep = true;
return '-'
} }
let parsedExpArr;
function processNrcc(messages) { // Remove newlines and then <p> tags from input and append to array if (expArr instanceof Date && !isNaN(expArr)) {
let arrMessages; if (!isEarlyArr && !isDelayedArr) {
if (!Array.isArray(messages)) { parsedExpArr = 'RT';
arrMessages = [messages];
} else { } else {
arrMessages = messages; parsedExpArr = parseIndividualTime(expArr);
} }
let processedMessages = []; } else {
for (const message of arrMessages) { parsedExpArr = '-';
const msgText = message.xhtmlMessage
processedMessages.push(msgText.replace(/[\n\r]/g, '').replace(/<\/?p[^>]*>/g, ''));
}
return processedMessages;
} }
onMount(() => { let parsedExpDep;
fetchData(); if (expDep instanceof Date && !isNaN(expDep)) {
if (!isEarlyDep && !isDelayedDep) {
parsedExpDep = 'RT';
} else {
parsedExpDep = parseIndividualTime(expDep);
}
} else {
parsedExpDep = '-';
}
return {
schArr: parseIndividualTime(schArr),
expArr: parsedExpArr,
schDep: parseIndividualTime(schDep),
expDep: parsedExpDep,
earArr: isEarlyArr,
delArr: isDelayedArr,
earDep: isEarlyDep,
delDep: isDelayedDep
};
}
function parseIndividualTime(input) {
const dt = new Date(input);
const output = dt.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
}); });
</script> if (output !== 'Invalid Date') {
return output;
}
return '-';
}
{#if isLoading} function processNrcc(messages) {
<Loading /> // Remove newlines and then <p> tags from input and append to array
{:else} let arrMessages;
{#if alerts.length} if (!Array.isArray(messages)) {
<AlertBar {alerts} /> arrMessages = [messages];
{/if} } else {
<table> arrMessages = messages;
<tr><td colspan="8" id="timestamp">Updated: {dataAge.toLocaleTimeString()} - Staff Boards under development</td></tr> }
<tr> let processedMessages = [];
<th class="id">ID</th> for (const message of arrMessages) {
<th class="from">From</th> const msgText = message.xhtmlMessage;
<th class="to">To</th> processedMessages.push(
<th class="plat">Plat</th> msgText.replace(/[\n\r]/g, '').replace(/<\/?p[^>]*>/g, '')
<th class="time">Sch Arr</th> );
<th class="time">Exp Arr</th> }
<th class="time">Sch Dep</th> return processedMessages;
<th class="time">Exp Dep</th> }
</tr>
{#each services as service}
{#await generateServiceData(service)}
<tr>
<td colspan="8">
Loading...
</td>
</tr>
{:then serviceStats}
<!-- Await a 'Generate Stats' function here which can evaluate the data and provide onMount(() => {
fetchData();
});
</script>
{#if isLoading}
<Loading />
{:else}
{#if alerts.length}
<AlertBar {alerts} />
{/if}
<table>
<tr
><td colspan="8" id="timestamp"
>Updated: {dataAge.toLocaleTimeString()} - Staff Boards under development</td
></tr
>
<tr>
<th class="id">ID</th>
<th class="from">From</th>
<th class="to">To</th>
<th class="plat">Plat</th>
<th class="time">Sch Arr</th>
<th class="time">Exp Arr</th>
<th class="time">Sch Dep</th>
<th class="time">Exp Dep</th>
</tr>
{#each services as service}
{#await generateServiceData(service)}
<tr>
<td colspan="8"> Loading... </td>
</tr>
{:then serviceStats}
<!-- Await a 'Generate Stats' function here which can evaluate the data and provide
relevant BOOLs like isCancelled, isEarly, isLate, isNonPassenger and calculate train length relevant BOOLs like isCancelled, isEarly, isLate, isNonPassenger and calculate train length
where 'length' is not provided but 'formation' is. --> where 'length' is not provided but 'formation' is. -->
<tr> <tr>
<td class="id id-data data">{service.trainid}</td> <td class="id id-data data">{service.trainid}</td>
<td class="from from-data data {serviceStats.isCancelled && 'can-dat'}">{serviceStats.from}</td> <td
<td class="to to-data data {serviceStats.isCancelled && 'can-dat'}">{serviceStats.to}</td> class="from from-data data {serviceStats.isCancelled && 'can-dat'}"
<td class="plat plat-data data {serviceStats.isCancelled && 'can-dat'} {serviceStats.platformHidden && 'hidden'}">{serviceStats.platform.number || '-'}</td> >{serviceStats.from}</td
<td class="time time-data data {serviceStats.isCancelled && 'can-dat'}">{serviceStats.schArr}</td> >
<td class="time time-data data {serviceStats.isArrDelayed && 'late'} {serviceStats.isEarlyArr && 'early'} {serviceStats.isLateArr && 'late'}">{serviceStats.isArrDelayed ? 'LATE' : serviceStats.expArr}</td> <td class="to to-data data {serviceStats.isCancelled && 'can-dat'}"
<td class="time time-data data {serviceStats.isCancelled && 'can-dat'}">{serviceStats.schDep}</td> >{serviceStats.to}</td
<td class="time time-data data {serviceStats.isDepDelayed && 'late'} {serviceStats.isEarlyDep && 'early'} {serviceStats.isLateDep && 'late'}">{serviceStats.isDepDelayed ? 'LATE' : serviceStats.expDep}</td> >
</tr> <td
<tr class="text-row"> class="plat plat-data data {serviceStats.isCancelled &&
<td colspan="8" class="text-data"> 'can-dat'} {serviceStats.platformHidden && 'hidden'}"
{service.operator} {#if serviceStats.length} | {serviceStats.length} carriages{/if} >{serviceStats.platform.number || '-'}</td
<br> >
{#if service.isCancelled} <td
{#await getReasonCodeData(service.cancelReason)} class="time time-data data {serviceStats.isCancelled && 'can-dat'}"
This train has been cancelled >{serviceStats.schArr}</td
{:then reasonCode} >
{reasonCode[0].cancReason} <td
<br> class="time time-data data {serviceStats.isArrDelayed &&
{/await} 'late'} {serviceStats.isEarlyArr &&
{/if} 'early'} {serviceStats.isLateArr && 'late'}"
{#if service?.delayReason} >{serviceStats.isArrDelayed ? 'LATE' : serviceStats.expArr}</td
{#await getReasonCodeData(service.delayReason)} >
This train has been delayed <td
{:then reasonCode} class="time time-data data {serviceStats.isCancelled && 'can-dat'}"
{reasonCode[0].lateReason} >{serviceStats.schDep}</td
<br> >
{/await} <td
{/if} class="time time-data data {serviceStats.isDepDelayed &&
</td> 'late'} {serviceStats.isEarlyDep &&
</tr> 'early'} {serviceStats.isLateDep && 'late'}"
{:catch} >{serviceStats.isDepDelayed ? 'LATE' : serviceStats.expDep}</td
<tr> >
<td colspan="8">Unable to load service</td> </tr>
</tr> <tr class="text-row">
{/await} <td colspan="8" class="text-data">
{/each} {service.operator}
</table> {#if serviceStats.length} | {serviceStats.length} carriages{/if}
{/if} <br />
{#if service.isCancelled}
{#await getReasonCodeData(service.cancelReason)}
This train has been cancelled
{:then reasonCode}
{reasonCode[0].cancReason}
<br />
{/await}
{/if}
{#if service?.delayReason}
{#await getReasonCodeData(service.delayReason)}
This train has been delayed
{:then reasonCode}
{reasonCode[0].lateReason}
<br />
{/await}
{/if}
</td>
</tr>
{:catch}
<tr>
<td colspan="8">Unable to load service</td>
</tr>
{/await}
{/each}
</table>
{/if}
<Nav /> <Nav />
<style> <style>
#timestamp { #timestamp {
color: var(--second-text-color); color: var(--second-text-color);
} }
@ -306,7 +343,8 @@ if (expDep instanceof Date && !isNaN(expDep)) {
text-align: left; text-align: left;
} }
.from-data, .to-data { .from-data,
.to-data {
color: yellow; color: yellow;
text-decoration: none; text-decoration: none;
text-align: left; text-align: left;
@ -323,42 +361,42 @@ if (expDep instanceof Date && !isNaN(expDep)) {
font-size: smaller; font-size: smaller;
} }
.can-dat { .can-dat {
color: grey; color: grey;
text-decoration: line-through; text-decoration: line-through;
} }
.hidden { .hidden {
color: grey; color: grey;
} }
.can-time { .can-time {
animation: pulse-cancel 1.5s linear infinite; animation: pulse-cancel 1.5s linear infinite;
} }
.early { .early {
animation: pulse-early 1.5s linear infinite; animation: pulse-early 1.5s linear infinite;
} }
.late { .late {
animation: pulse-late 1.5s linear infinite; animation: pulse-late 1.5s linear infinite;
} }
@keyframes pulse-late { @keyframes pulse-late {
50% { 50% {
color: var(--main-warning-color); color: var(--main-warning-color);
} }
} }
@keyframes pulse-cancel { @keyframes pulse-cancel {
50% { 50% {
color: var(--main-alert-color); color: var(--main-alert-color);
} }
} }
@keyframes pulse-early { @keyframes pulse-early {
50% { 50% {
color: rgb(136, 164, 255); color: rgb(136, 164, 255);
} }
} }
</style> </style>

View File

@ -1,31 +1,31 @@
<div id="container"> <div id="container">
<p id="tick">&#10004;</p> <p id="tick">&#10004;</p>
<p>Done</p> <p>Done</p>
</div> </div>
<style> <style>
#tick { #tick {
font-size: 45px; font-size: 45px;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
#container { #container {
position:fixed; position: fixed;
top:50%; top: 50%;
left:50%; left: 50%;
transform:translate(-50%,-50%); transform: translate(-50%, -50%);
margin:auto; margin: auto;
background-color:var(--overlay-color); background-color: var(--overlay-color);
border-radius:15px; border-radius: 15px;
padding:20px; padding: 20px;
padding-bottom:1px; padding-bottom: 1px;
min-width:90px; min-width: 90px;
max-width:90px max-width: 90px;
} }
p { p {
padding-top:0px; padding-top: 0px;
font-weight:bolder; font-weight: bolder;
overflow-wrap:normal; overflow-wrap: normal;
color: white; color: white;
} }
</style> </style>

View File

@ -1,40 +1,42 @@
<script> <script>
export let title = 'title' export let title = 'title';
</script> </script>
<div class="headerBar"> <div class="headerBar">
<a href="/"> <a href="/">
<picture> <picture>
<source srcset="/images/logo/wide_logo.svg" type="image/svg+xml"> <source srcset="/images/logo/wide_logo.svg" type="image/svg+xml" />
<img src="/images/logo/wide_logo_200.png" alt="OwlBoard Logo"> <img src="/images/logo/wide_logo_200.png" alt="OwlBoard Logo" />
</picture> </picture>
</a> </a>
<header>{title}</header> <header>{title}</header>
</div> </div>
<div class="headerBlock"> <div class="headerBlock">
<!-- This exists to prevent the headerBar overlapping anything below it --> <!-- This exists to prevent the headerBar overlapping anything below it -->
</div> </div>
<style> <style>
.headerBar { .headerBar {
background: var(--overlay-color-solid); background: var(--overlay-color-solid);
color: var(--main-text-color); color: var(--main-text-color);
position: fixed; position: fixed;
top: 0; left: 0; top: 0;
left: 0;
width: 100%; width: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 50px; height: 50px;
z-index: 20; z-index: 20;
} }
img { img {
position: absolute; position: absolute;
height: 40px; height: 40px;
right: 8px; right: 8px;
top: 5px; top: 5px;
} }
header { header {
position: absolute; position: absolute;
left: 0px; left: 0px;
top: 3px; top: 3px;
@ -43,11 +45,11 @@ header {
margin-top: 7px; margin-top: 7px;
margin-left: 20px; margin-left: 20px;
font-size: 15pt; font-size: 15pt;
} }
.headerBlock { .headerBlock {
height: 50px; height: 50px;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
</style> </style>

View File

@ -1,53 +1,57 @@
<script> <script>
import { fade } from "svelte/transition"; import { fade } from 'svelte/transition';
</script> </script>
<div id="container" in:fade={{delay: 150, duration: 250}} out:fade={{duration: 250}}> <div
<div class="spinner"></div> id="container"
<p>Loading...</p> in:fade={{ delay: 150, duration: 250 }}
out:fade={{ duration: 250 }}
>
<div class="spinner" />
<p>Loading...</p>
</div> </div>
<style> <style>
@keyframes spinner { @keyframes spinner {
0% { 0% {
transform:translate3d(-50%,-50%,0) rotate(0) transform: translate3d(-50%, -50%, 0) rotate(0);
} }
100% { 100% {
transform:translate3d(-50%,-50%,0) rotate(360deg) transform: translate3d(-50%, -50%, 0) rotate(360deg);
} }
} }
.spinner::before { .spinner::before {
animation:1.5s linear infinite spinner; animation: 1.5s linear infinite spinner;
animation-play-state:inherit; animation-play-state: inherit;
border:solid 5px var(--overlay-color); border: solid 5px var(--overlay-color);
border-bottom-color: white; border-bottom-color: white;
border-radius:50%; border-radius: 50%;
content:""; content: '';
height:40px; height: 40px;
width:40px; width: 40px;
position:absolute; position: absolute;
top:30%; top: 30%;
margin:auto; margin: auto;
transform:translate3d(-50%,-50%,0); transform: translate3d(-50%, -50%, 0);
will-change:transform will-change: transform;
} }
#container { #container {
position:fixed; position: fixed;
top:50%; top: 50%;
left:50%; left: 50%;
transform:translate(-50%,-50%); transform: translate(-50%, -50%);
margin:auto; margin: auto;
background-color:var(--overlay-color); background-color: var(--overlay-color);
border-radius:15px; border-radius: 15px;
padding:20px; padding: 20px;
padding-bottom:1px; padding-bottom: 1px;
min-width:90px; min-width: 90px;
max-width:90px max-width: 90px;
} }
p { p {
padding-top:50px; padding-top: 50px;
font-weight:bolder; font-weight: bolder;
overflow-wrap:normal; overflow-wrap: normal;
color: white; color: white;
} }
</style> </style>

View File

@ -1,91 +1,103 @@
<script> <script>
const links = [ const links = [
{ {
title: "Home", title: 'Home',
path: "/", path: '/',
svgPath: "/images/navigation/home.svg" svgPath: '/images/navigation/home.svg'
} }
] ];
import { page } from "$app/stores"; import { page } from '$app/stores';
</script> </script>
<footer> <footer>
{#each links as item} {#each links as item}
<a class="footerLink" href={item.path} class:active={$page.url.pathname == item.path}> <a
<img src="{item.svgPath}" alt="{item.title}"> class="footerLink"
<br> href={item.path}
<span>{item.title}</span> class:active={$page.url.pathname == item.path}
</a> >
<img src={item.svgPath} alt={item.title} />
<br />
<span>{item.title}</span>
</a>
{/each} {/each}
<div class="data-source"> <div class="data-source">
<a href="https://nationalrail.co.uk" target="_blank"> <a href="https://nationalrail.co.uk" target="_blank">
<picture> <picture>
<source srcset="/images/nre/nre-powered_200w.jxl" type="image/jxl"> <source srcset="/images/nre/nre-powered_200w.jxl" type="image/jxl" />
<source srcset="/images/nre/nre-powered_200w.webp" type="image/webp"> <source srcset="/images/nre/nre-powered_200w.webp" type="image/webp" />
<img id="nre-logo" src="/images/nre/nre-powered_200w.png" alt="Data sourced from National Rail and others"> <img
id="nre-logo"
src="/images/nre/nre-powered_200w.png"
alt="Data sourced from National Rail and others"
/>
</picture> </picture>
</a> </a>
</div> </div>
</footer> </footer>
<style> <style>
footer { footer {
position: fixed; position: fixed;
display: flex; display: flex;
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 50px; height: 50px;
background-color: var(--overlay-color); background-color: var(--overlay-color);
} }
.footerLink { .footerLink {
width: 30%; width: 30%;
height: 100%; height: 100%;
background-color: var(--overlay-color); background-color: var(--overlay-color);
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-top: none; border-top: none;
border-bottom: none; border-bottom: none;
border-color: rgba(0, 0, 0, 0.24); border-color: rgba(0, 0, 0, 0.24);
text-decoration: double; text-decoration: double;
font-weight: 600; font-weight: 600;
} }
footer a.active { footer a.active {
background-color: transparent; background-color: transparent;
} }
.data-source {
flex-grow: 2;
background: rgb(255, 255, 255);
}
#nre-logo {
width: 150px;
height: auto;
margin: auto;
margin-top: 13px;
}
@media only screen and (min-width: 475px) {
.data-source { .data-source {
flex-grow: 2; background: rgb(255, 255, 255);
background: rgb(255,255,255); background: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 1) 40%
);
} }
#nre-logo { #nre-logo {
width: 150px; position: absolute;
height: auto; right: 0;
margin: auto; right: 20px;
margin-top: 13px;
}
@media only screen and (min-width: 475px) {
.data-source {
background: rgb(255,255,255);
background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 40%);
}
#nre-logo {
position: absolute;
right: 0;
right: 20px;
}
}
img {
height: 20px;
width: 20px;
margin: 0;
margin-top: 3px;
padding: 0;
}
span {
margin: 0;
margin-bottom: 3px;
padding: 0;
} }
}
img {
height: 20px;
width: 20px;
margin: 0;
margin-top: 3px;
padding: 0;
}
span {
margin: 0;
margin-bottom: 3px;
padding: 0;
}
</style> </style>

View File

@ -1,74 +1,74 @@
<script> <script>
const links = [ const links = [
{ {
title: "Home", title: 'Home',
path: "/", path: '/',
svgPath: "/images/navigation/home.svg" svgPath: '/images/navigation/home.svg'
}, },
{ {
title: "PIS Finder", title: 'PIS Finder',
path: "/pis", path: '/pis',
svgPath: "/images/navigation/info.svg" svgPath: '/images/navigation/info.svg'
}, },
{ {
title: "More", title: 'More',
path: "/more", path: '/more',
svgPath: "/images/navigation/more.svg" svgPath: '/images/navigation/more.svg'
} }
] ];
import { page } from "$app/stores"; import { page } from '$app/stores';
</script> </script>
<footer> <footer>
{#each links as item} {#each links as item}
<a href={item.path} class:active={$page.url.pathname == item.path}> <a href={item.path} class:active={$page.url.pathname == item.path}>
<img src="{item.svgPath}" alt="{item.title}"> <img src={item.svgPath} alt={item.title} />
<br> <br />
<span>{item.title}</span> <span>{item.title}</span>
</a> </a>
{/each} {/each}
</footer> </footer>
<style> <style>
footer { footer {
position: fixed; position: fixed;
display: flex; display: flex;
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 50px; height: 50px;
background-color: rgb(54, 54, 54); background-color: rgb(54, 54, 54);
} }
footer a { footer a {
flex: 12; flex: 12;
width: 30%; width: 30%;
height: 100%; height: 100%;
background-color: var(--overlay-color); background-color: var(--overlay-color);
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-top: none; border-top: none;
border-bottom: none; border-bottom: none;
border-color: rgba(0, 0, 0, 0.24); border-color: rgba(0, 0, 0, 0.24);
text-decoration: double; text-decoration: double;
font-weight: 600; font-weight: 600;
} }
footer a.active { footer a.active {
background-color: transparent; background-color: transparent;
} }
img { img {
height: 20px; height: 20px;
width: 20px; width: 20px;
margin: 0; margin: 0;
margin-top: 3px; margin-top: 3px;
padding: 0; padding: 0;
} }
span { span {
margin: 0; margin: 0;
margin-bottom: 3px; margin-bottom: 3px;
padding: 0; padding: 0;
} }
</style> </style>

View File

@ -1,49 +1,49 @@
<script> <script>
import OverlayIsland from "$lib/islands/overlay-island.svelte"; import OverlayIsland from '$lib/islands/overlay-island.svelte';
const variables = { const variables = {
title: "Welcome to OwlBoard" title: 'Welcome to OwlBoard'
} };
const version = "2023.7.1" const version = '2023.7.1';
let pageNum = 0; let pageNum = 0;
function pageUp() { function pageUp() {
pageNum ++; pageNum++;
console.log(`Welcome page: ${pageNum}`) console.log(`Welcome page: ${pageNum}`);
} }
function pageDn() { function pageDn() {
pageNum --; pageNum--;
console.log(`Welcome page: ${pageNum}`) console.log(`Welcome page: ${pageNum}`);
} }
const pageText = [ const pageText = [
"<h3>A brand new look</h3>" + '<h3>A brand new look</h3>' +
"<p>OwlBoard has a brand new look, making it even faster for you to access the data</p>" + '<p>OwlBoard has a brand new look, making it even faster for you to access the data</p>' +
"<p>If you have signed up before, you won't have to to it again and any customised Quick Links are here waiting for you</p>", "<p>If you have signed up before, you won't have to to it again and any customised Quick Links are here waiting for you</p>",
"<h3>Faster Access</h3>" + '<h3>Faster Access</h3>' +
"<p>Both live station data, and timetable search is available from the homepage. Making it faster to get the info you need</p>" + '<p>Both live station data, and timetable search is available from the homepage. Making it faster to get the info you need</p>' +
"<p>Search the timetable using a headcode to see a trains details - OwlBoard now shows data for all TOCs and FOCs.</p>" + '<p>Search the timetable using a headcode to see a trains details - OwlBoard now shows data for all TOCs and FOCs.</p>' +
"<p>For GWR services: if a PIS code is available for a service, you'll see it alongside the train details.</p>", "<p>For GWR services: if a PIS code is available for a service, you'll see it alongside the train details.</p>",
"<h3>PIS Finder</h3>" + '<h3>PIS Finder</h3>' +
"<p>Don't worry, the PIS finder hasn't gone away. It has even been moved to the new navigation bar for faster access</p>" + "<p>Don't worry, the PIS finder hasn't gone away. It has even been moved to the new navigation bar for faster access</p>" +
"<p>If there isn't a PIS code available for a given headcode, you can use this tool to search by start and end stations, enabling you to utilise a different code and skipping stops as needed.</p>", "<p>If there isn't a PIS code available for a given headcode, you can use this tool to search by start and end stations, enabling you to utilise a different code and skipping stops as needed.</p>",
"<h3>Everything Else</h3>" + '<h3>Everything Else</h3>' +
"<p>Everything else has moved to the 'More' menu, where you'll find the Reference Code lookup and software details." "<p>Everything else has moved to the 'More' menu, where you'll find the Reference Code lookup and software details."
] ];
</script> </script>
<OverlayIsland {variables}> <OverlayIsland {variables}>
<h2>What's new in OwlBoard {version}</h2> <h2>What's new in OwlBoard {version}</h2>
<div> <div>
{@html pageText[pageNum]} {@html pageText[pageNum]}
</div> </div>
<button type="button" on:click={pageDn}>&lt;</button> <button type="button" on:click={pageDn}>&lt;</button>
<button type="button" on:click={pageUp}>&gt;</button> <button type="button" on:click={pageUp}>&gt;</button>
</OverlayIsland> </OverlayIsland>
<style> <style>
div { div {
color: white; color: white;
} }
</style> </style>

View File

@ -1,29 +1,28 @@
import { writable } from 'svelte/store' import { writable } from 'svelte/store';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export const ql = writable(fromLocalStorage('ql', [])) export const ql = writable(fromLocalStorage('ql', []));
toLocalStorage(ql, 'ql'); toLocalStorage(ql, 'ql');
function fromLocalStorage(storageKey, fallback) { function fromLocalStorage(storageKey, fallback) {
if (browser) { if (browser) {
const storedValue = localStorage.getItem(storageKey); const storedValue = localStorage.getItem(storageKey);
if (storedValue !== 'undefined' && storedValue !== null) { if (storedValue !== 'undefined' && storedValue !== null) {
return (typeof fallback === 'object') return typeof fallback === 'object'
? JSON.parse(storedValue) ? JSON.parse(storedValue)
: storedValue : storedValue;
}
} }
return fallback }
return fallback;
} }
function toLocalStorage(store, storageKey) { function toLocalStorage(store, storageKey) {
if (browser) { if (browser) {
store.subscribe(value => { store.subscribe((value) => {
let storageValue = (typeof value === 'object') let storageValue =
? JSON.stringify(value) typeof value === 'object' ? JSON.stringify(value) : value;
: value
localStorage.setItem(storageKey, storageValue) localStorage.setItem(storageKey, storageValue);
}) });
} }
} }

View File

@ -1,23 +1,23 @@
import { writable } from 'svelte/store' import { writable } from 'svelte/store';
import { browser } from '$app/environment'; import { browser } from '$app/environment';
export const uuid = writable(fromLocalStorage('uuid', null)) export const uuid = writable(fromLocalStorage('uuid', null));
toLocalStorage(uuid, 'uuid'); toLocalStorage(uuid, 'uuid');
function fromLocalStorage(storageKey, fallback) { function fromLocalStorage(storageKey, fallback) {
if (browser) { if (browser) {
const storedValue = localStorage.getItem(storageKey); const storedValue = localStorage.getItem(storageKey);
if (storedValue !== 'undefined') { if (storedValue !== 'undefined') {
return storedValue return storedValue;
}
} }
return fallback }
return fallback;
} }
function toLocalStorage(store, storageKey) { function toLocalStorage(store, storageKey) {
if (browser) { if (browser) {
store.subscribe(value => { store.subscribe((value) => {
localStorage.setItem(storageKey, value) localStorage.setItem(storageKey, value);
}) });
} }
} }

View File

@ -1,114 +1,133 @@
<script> <script>
import { fly } from "svelte/transition"; import { fly } from 'svelte/transition';
export let service = "" export let service = '';
let isExpanded = false; let isExpanded = false;
async function expand() {
isExpanded = !isExpanded
}
async function expand() {
isExpanded = !isExpanded;
}
</script> </script>
<div class="container"> <div class="container">
<div class="container-header" on:click={expand} on:keypress={expand}> <div class="container-header" on:click={expand} on:keypress={expand}>
<span class="header">{service.operator || "GW"}: {service.stops[0]['publicDeparture'] || service.stops[0]['wttDeparture']} {service.stops[0]['tiploc']} to {service.stops[service['stops'].length -1]['tiploc']}</span> <span class="header"
<span id="container-arrow" class:isExpanded>V</span> >{service.operator || 'GW'}: {service.stops[0]['publicDeparture'] ||
</div> service.stops[0]['wttDeparture']}
{#if isExpanded} {service.stops[0]['tiploc']} to {service.stops[
<div class="container-detail" in:fly={{ y: -20, duration: 200}}> service['stops'].length - 1
{#if service.pis} ]['tiploc']}</span
<p class="pis">PIS: {service.pis}</p> >
<span id="container-arrow" class:isExpanded>V</span>
</div>
{#if isExpanded}
<div class="container-detail" in:fly={{ y: -20, duration: 200 }}>
{#if service.pis}
<p class="pis">PIS: {service.pis}</p>
{/if}
<p class="svc-detail">
Planned Type: {parseInt(service.planSpeed) || 68}mph {service.powerType ||
'Bus'}
</p>
<p class="svc-detail">
Days Run: {service.daysRun.join(', ').toUpperCase()}
</p>
<p class="svc-detail validity">
Valid From: {new Date(service.scheduleStartDate).toLocaleDateString(
'en-GB',
{
timeZone: 'UTC'
}
)} - {new Date(service.scheduleEndDate).toLocaleDateString('en-GB', {
timeZone: 'UTC'
})}
</p>
<table>
<tr>
<th>Location</th>
<th>Sch Arr.</th>
<th>Sch Dep.</th>
</tr>
{#if service.stops[0]['publicDeparture']}
{#each service.stops as stop}
{#if stop.publicArrival || stop.publicDeparture}
<tr>
<td>{stop.tiploc}</td>
<td>{stop.publicArrival || '-'}</td>
<td>{stop.publicDeparture || '-'}</td>
</tr>
{/if} {/if}
<p class="svc-detail">Planned Type: {parseInt(service.planSpeed) || 68 }mph {service.powerType || "Bus" }</p> {/each}
<p class="svc-detail">Days Run: {service.daysRun.join(", ").toUpperCase()}</p> {:else}
<p class="svc-detail validity">Valid From: {new Date(service.scheduleStartDate).toLocaleDateString('en-GB', {timeZone: 'UTC'})} - {new Date(service.scheduleEndDate).toLocaleDateString('en-GB', {timeZone: 'UTC'})}</p> {#each service.stops as stop}
<table> <tr>
<tr> <td>{stop.tiploc}</td>
<th>Location</th> <td>{stop.wttArrival || '-'}</td>
<th>Sch Arr.</th> <td>{stop.wttDeparture || '-'}</td>
<th>Sch Dep.</th> </tr>
</tr> {/each}
{#if service.stops[0]['publicDeparture']} {/if}
{#each service.stops as stop} </table>
{#if stop.publicArrival || stop.publicDeparture} </div>
<tr> {/if}
<td>{stop.tiploc}</td>
<td>{stop.publicArrival || '-'}</td>
<td>{stop.publicDeparture || '-'}</td>
</tr>
{/if}
{/each}
{:else}
{#each service.stops as stop}
<tr>
<td>{stop.tiploc}</td>
<td>{stop.wttArrival || '-'}</td>
<td>{stop.wttDeparture || '-'}</td>
</tr>
{/each}
{/if}
</table>
</div>
{/if}
</div> </div>
<style> <style>
.container { .container {
position: relative; position: relative;
margin: auto; margin: auto;
margin-bottom: 20px; margin-bottom: 20px;
width: 95%; width: 95%;
max-width: 600px; max-width: 600px;
height: auto; height: auto;
background-color: var(--overlay-color); background-color: var(--overlay-color);
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
transition: height 500ms ease-in-out; transition: height 500ms ease-in-out;
} }
.container-header { .container-header {
text-align: left; text-align: left;
padding-left: 10px; padding-left: 10px;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
padding-top: 12px; padding-top: 12px;
padding-bottom: 10px; padding-bottom: 10px;
} }
#container-arrow { #container-arrow {
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
font-weight: 600; font-weight: 600;
color: white; color: white;
margin: 0; margin: 0;
padding: 0; padding: 0;
position: absolute; position: absolute;
right: 16px; right: 16px;
top: 13px; top: 13px;
transition-duration: 300ms; transition-duration: 300ms;
z-index: 2; z-index: 2;
} }
.isExpanded { .isExpanded {
transform: rotate(180deg); transform: rotate(180deg);
} }
.pis { .pis {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: azure; color: azure;
margin-top: 10px; margin-top: 10px;
margin-bottom: 5px; margin-bottom: 5px;
} }
.svc-detail { .svc-detail {
margin-top: 2px; margin-top: 2px;
margin-bottom: 2px; margin-bottom: 2px;
color: white; color: white;
} }
.validity { .validity {
font-size: 14px; font-size: 14px;
} }
table { table {
margin: auto; margin: auto;
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px; padding-bottom: 10px;
color: white; color: white;
} }
</style> </style>

View File

@ -1,8 +1,8 @@
<script> <script>
import { page } from '$app/stores'; import { page } from '$app/stores';
import Header from '$lib/navigation/header.svelte'; import Header from '$lib/navigation/header.svelte';
import Nav from '$lib/navigation/nav.svelte'; import Nav from '$lib/navigation/nav.svelte';
const title = "OwlBoard - Error" const title = 'OwlBoard - Error';
</script> </script>
<Header {title} /> <Header {title} />
@ -10,15 +10,22 @@
<h1>{$page.status}: {$page?.error?.message}</h1> <h1>{$page.status}: {$page?.error?.message}</h1>
{#if $page.status === 404} {#if $page.status === 404}
<p>This is not the page you're looking for.</p> <p>This is not the page you're looking for.</p>
<p>The page you are looking for doesn't exist, use the tabs below to find another page.</p> <p>
The page you are looking for doesn't exist, use the tabs below to find
another page.
</p>
{:else if $page.status === 500} {:else if $page.status === 500}
<p>Something went wrong loading the app.<br> <p>
Try going <a href="/">home</a> and try again.</p> Something went wrong loading the app.<br />
<p>If the problem persists, you can report an issue from the 'More' menu.</p> Try going <a href="/">home</a> and try again.
</p>
<p>If the problem persists, you can report an issue from the 'More' menu.</p>
{:else} {:else}
<p>Not sure what went wrong, please try again later or report an issue from <p>
the 'More' menu.</p> Not sure what went wrong, please try again later or report an issue from the
'More' menu.
</p>
{/if} {/if}
<Nav /> <Nav />

View File

@ -1,16 +1,20 @@
<script> <script>
import '../app.css' import '../app.css';
</script> </script>
<svelte:head> <svelte:head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="application-name" content="OwlBoard"> <meta name="application-name" content="OwlBoard" />
<meta name="author" content="Frederick Boniface"> <meta name="author" content="Frederick Boniface" />
<meta name="description" content="Live train data, PIS Codes & reference data. Built by railway staff, for railway staff."> <meta
<meta name="viewport" content="width=device-width"> name="description"
<meta name="theme-color" content="#00b7b7"> content="Live train data, PIS Codes & reference data. Built by railway staff, for railway staff."
<link rel="icon" href="/images/icon.svg" type="image/svg+xml"> />
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png"> <meta name="viewport" content="width=device-width" />
<link rel="manifest" href="/manifest.json"> <meta name="theme-color" content="#00b7b7" />
<title>OwlBoard</title> <link rel="icon" href="/images/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/images/app-icons/any/apple-192.png" />
<link rel="manifest" href="/manifest.json" />
<title>OwlBoard</title>
</svelte:head> </svelte:head>
<slot /> <slot />

View File

@ -1,28 +1,27 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
import InputIsland from '$lib/islands/input-island-form.svelte' import InputIsland from '$lib/islands/input-island-form.svelte';
import QuickLinkIsland from '$lib/islands/quick-link-island.svelte'; import QuickLinkIsland from '$lib/islands/quick-link-island.svelte';
import Welcome from '$lib/overlays/welcome.svelte'; import Welcome from '$lib/overlays/welcome.svelte';
const title = "OwlBoard" const title = 'OwlBoard';
const inputIslands = [ const inputIslands = [
{ {
title: "Live Departure Boards", title: 'Live Departure Boards',
action: "/ldb", action: '/ldb',
placeholder: "Enter CRS/TIPLOC", placeholder: 'Enter CRS/TIPLOC',
queryName: "station" queryName: 'station'
}, },
{ {
title: "Train Details & PIS", title: 'Train Details & PIS',
action: "/train", action: '/train',
placeholder: "Enter Headcode", placeholder: 'Enter Headcode',
queryName: "headcode" queryName: 'headcode'
} }
] ];
const isWelcomed = "false"; // Usually a bool - <Welcome /> is incomplete so should be hidden for now.
const isWelcomed = 'false'; // Usually a bool - <Welcome /> is incomplete so should be hidden for now.
</script> </script>
{#if !isWelcomed} {#if !isWelcomed}
@ -31,7 +30,7 @@ const isWelcomed = "false"; // Usually a bool - <Welcome /> is incomplete so sho
<Header {title} /> <Header {title} />
{#each inputIslands as variables} {#each inputIslands as variables}
<InputIsland {variables} /> <InputIsland {variables} />
{/each} {/each}
<QuickLinkIsland /> <QuickLinkIsland />

View File

@ -1,43 +1,42 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Nav from '$lib/navigation/nav-ldb.svelte' import Nav from '$lib/navigation/nav-ldb.svelte';
import PublicLdb from '$lib/ldb/public-ldb.svelte'; import PublicLdb from '$lib/ldb/public-ldb.svelte';
import StaffLdb from '$lib/ldb/staff-ldb.svelte'; import StaffLdb from '$lib/ldb/staff-ldb.svelte';
import { uuid } from '$lib/stores/uuid.js'; import { uuid } from '$lib/stores/uuid.js';
import {onMount} from 'svelte' import { onMount } from 'svelte';
let title = "Loading" let title = 'Loading';
async function getHeadcode() { async function getHeadcode() {
return new URLSearchParams(window.location.search).get('station'); return new URLSearchParams(window.location.search).get('station');
}
let station = '';
let staff = false;
let uuidValue = '';
$: uuidValue = $uuid;
onMount(async () => {
station = (await getHeadcode()) || '';
if (uuidValue !== null && uuidValue !== '' && uuidValue !== 'null') {
staff = true;
title = 'Staff Board';
} else {
title = 'Public Board';
} }
});
let station = "";
let staff = false;
let uuidValue = "";
$: uuidValue = $uuid;
onMount(async () => {
station = await getHeadcode() || "";
if (uuidValue !== null && uuidValue !== "" && uuidValue !== "null") {
staff = true;
title = "Staff Board"
} else {
title = "Public Board"
}
})
</script> </script>
<Header {title} /> <Header {title} />
<!-- If 'uuid' exists in store then load StaffLdb else load PublicLdb --> <!-- If 'uuid' exists in store then load StaffLdb else load PublicLdb -->
{#if !staff} {#if !staff}
<PublicLdb {station} bind:title={title} /> <PublicLdb {station} bind:title />
{:else} {:else}
<StaffLdb {station} bind:title={title} /> <StaffLdb {station} bind:title />
<!--<StaffLdb {station} bind:title={title} /> -- Temporary, Disable StaffLdb - it isn't implemented --> <!--<StaffLdb {station} bind:title={title} /> -- Temporary, Disable StaffLdb - it isn't implemented -->
{/if} {/if}
<Nav /> <Nav />

View File

@ -1,68 +1,68 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
const title = "More" const title = 'More';
const links = [ const links = [
{title: "Your Data", path: "/more/data"}, { title: 'Your Data', path: '/more/data' },
{title: "Registration", path: "/more/reg"}, { title: 'Registration', path: '/more/reg' },
{title: "Location Reference Code Lookup", path: "/more/corpus"}, { title: 'Location Reference Code Lookup', path: '/more/corpus' },
{title: "Reason Code Lookup", path: "/more/reasons"}, { title: 'Reason Code Lookup', path: '/more/reasons' },
{title: "Privacy Policy", path: "/more/privacy"}, { title: 'Privacy Policy', path: '/more/privacy' },
{title: "Component Versions", path: "/more/versions"}, { title: 'Component Versions', path: '/more/versions' },
{title: "Statictics", path: "/more/statistics"}, { title: 'Statictics', path: '/more/statistics' },
{title: "Settings", path: "/more/settings"}, { title: 'Settings', path: '/more/settings' },
{title: "Report Issue", path: "/more/report"}, { title: 'Report Issue', path: '/more/report' },
{title: "About", path: "/more/about"} { title: 'About', path: '/more/about' }
] ];
</script> </script>
<Header {title} /> <Header {title} />
{#each links as item} {#each links as item}
<a href={item.path}> <a href={item.path}>
<div> <div>
<p>{item.title}</p> <p>{item.title}</p>
</div> </div>
</a> </a>
{/each} {/each}
<Nav /> <Nav />
<style> <style>
div { div {
width: 100%; width: 100%;
background: rgba(0, 0, 0, 0.226); background: rgba(0, 0, 0, 0.226);
border-color: aliceblue; border-color: aliceblue;
border-width: 1px; border-width: 1px;
border-style: solid; border-style: solid;
border-left: none; border-left: none;
border-right: none; border-right: none;
height: 50px; height: 50px;
} }
a { a {
text-decoration: none; text-decoration: none;
height: 100%; height: 100%;
vertical-align: middle; vertical-align: middle;
} }
p {
color: white;
margin: 0;
padding-left: 10px;
text-align: left;
line-height: 50px;
vertical-align: middle;
font-weight: 600;
font-size: 20px;
}
@media (min-width: 600px) {
p { p {
color: white; text-align: center;
margin: 0;
padding-left: 10px;
text-align: left;
line-height: 50px;
vertical-align: middle;
font-weight: 600;
font-size: 20px;
} }
@media (min-width: 600px) { }
p { @media (max-width: 380px) {
text-align: center; p {
} font-size: 16px;
} }
@media (max-width: 380px) { }
p { </style>
font-size: 16px;
}
}
</style>

View File

@ -1,26 +1,32 @@
<script> <script>
import LargeLogo from "$lib/images/large-logo.svelte"; import LargeLogo from '$lib/images/large-logo.svelte';
import Header from "$lib/navigation/header.svelte"; import Header from '$lib/navigation/header.svelte';
import Nav from "$lib/navigation/nav.svelte"; import Nav from '$lib/navigation/nav.svelte';
const title = "About"; const title = 'About';
</script> </script>
<Header {title}/>
<Header {title} />
<LargeLogo /> <LargeLogo />
<p class="neg">&copy; 2022-2023 Frederick Boniface</p> <p class="neg">&copy; 2022-2023 Frederick Boniface</p>
<p>OwlBoard was created by train-crew for train-crew</p> <p>OwlBoard was created by train-crew for train-crew</p>
<p>I created OwlBoard in 2022 to freely give fast and easy access to the information <p>
that we need every day.</p> I created OwlBoard in 2022 to freely give fast and easy access to the
<p>Data sourced from: National Rail Enquiries, OwlBoard Project and Network Rail.</p> information that we need every day.
<p>OwlBoard components are available under various Open Source licenses, see the </p>
<a href="https://git.fjla.uk/OwlBoard" target="_blank">code repository</a> <p>
for more information. Data sourced from: National Rail Enquiries, OwlBoard Project and Network Rail.
</p>
<p>
OwlBoard components are available under various Open Source licenses, see the
<a href="https://git.fjla.uk/OwlBoard" target="_blank">code repository</a>
for more information.
</p> </p>
<Nav /> <Nav />
<style> <style>
.neg { .neg {
margin-top: -40px; margin-top: -40px;
margin-bottom: 40px; margin-bottom: 40px;
} }
</style> </style>

View File

@ -1,72 +1,71 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
const title = "Location Codes" const title = 'Location Codes';
let val = { let val = {
crs: "", crs: '',
tiploc: "", tiploc: '',
stanox: "", stanox: '',
nlc: "", nlc: '',
name: "", name: '',
uic: "" uic: ''
};
let isLoading = false;
async function getData(type = '', value = '') {
const url = `https://owlboard.info/api/v2/ref/locationCode/${type}/${value}`;
const res = await fetch(url);
const data = await res.json();
isLoading = false;
return data;
}
async function processData(data) {
//console.log("data",JSON.stringify(data))
if (data.ERROR == 'Offline') {
return;
} }
val = {
crs: data[0]['3ALPHA'] || 'None',
tiploc: data[0]['TIPLOC'] || 'None',
stanox: data[0]['STANOX'] || 'None',
nlc: data[0]['NLC'] || 'None',
name: data[0]['NLCDESC'] || 'None',
uic: data[0]['UIC'] || 'None'
};
//console.log("val",JSON.stringify(val));
}
let isLoading = false; async function submit() {
isLoading = true;
async function getData(type = "", value = "") { let data = [];
const url = `https://owlboard.info/api/v2/ref/locationCode/${type}/${value}` if (val?.crs) {
const res = await fetch(url); data = await getData('crs', val.crs);
const data = await res.json(); } else if (val?.tiploc) {
isLoading = false; data = await getData('tiploc', val.tiploc);
return data; } else if (val?.stanox) {
} data = await getData('stanox', val.stanox);
} else if (val?.nlc) {
async function processData(data) { data = await getData('nlc', val.nlc);
//console.log("data",JSON.stringify(data)) } else {
if (data.ERROR == "Offline") { return;
return;
};
val = {
crs: data[0]['3ALPHA'] || "None",
tiploc: data[0]['TIPLOC'] || "None",
stanox: data[0]['STANOX'] || "None",
nlc: data[0]['NLC'] || "None",
name: data[0]['NLCDESC'] || "None",
uic: data[0]['UIC'] || "None"
}
//console.log("val",JSON.stringify(val));
}
async function submit() {
isLoading = true;
let data = [];
if (val?.crs) {
data = await getData("crs", val.crs);
} else if (val?.tiploc) {
data = await getData("tiploc", val.tiploc);
} else if (val?.stanox) {
data = await getData("stanox", val.stanox);
} else if (val?.nlc) {
data = await getData("nlc", val.nlc);
} else {
return;
}
processData(data);
}
async function reset() {
val = {
crs: "",
tiploc: "",
stanox: "",
nlc: "",
name: "",
uic: "",
}
} }
processData(data);
}
async function reset() {
val = {
crs: '',
tiploc: '',
stanox: '',
nlc: '',
name: '',
uic: ''
};
}
</script> </script>
<Header {title} /> <Header {title} />
@ -80,54 +79,85 @@
<p class="desc">Some locations only have some applicable location codes.</p> <p class="desc">Some locations only have some applicable location codes.</p>
<div class="inputs"> <div class="inputs">
<form on:submit={submit}> <form on:submit={submit}>
<input type="text" readonly placeholder="Name" bind:value={val.name}> <input type="text" readonly placeholder="Name" bind:value={val.name} />
<br> <br />
<input type="text" maxlength="3" autocomplete="off" placeholder="CRS/3ALPHA" bind:value={val.crs}> <input
<br> type="text"
<input type="text" maxlength="7" autocomplete="off" placeholder="TIPLOC" bind:value={val.tiploc}> maxlength="3"
<br> autocomplete="off"
<input type="text" maxlength="10" autocomplete="off" placeholder="STANOX" bind:value={val.stanox}> placeholder="CRS/3ALPHA"
<br> bind:value={val.crs}
<input type="number" maxlength="6" min="0" autocomplete="off" placeholder="NLC" bind:value={val.nlc}> />
<br> <br />
<input type="text" readonly autocomplete="off" placeholder="UIC" bind:value={val.uic}> <input
<br> type="text"
<button type="submit">Submit</button> maxlength="7"
<button type="reset" on:click={reset}>Reset</button> autocomplete="off"
</form> placeholder="TIPLOC"
bind:value={val.tiploc}
/>
<br />
<input
type="text"
maxlength="10"
autocomplete="off"
placeholder="STANOX"
bind:value={val.stanox}
/>
<br />
<input
type="number"
maxlength="6"
min="0"
autocomplete="off"
placeholder="NLC"
bind:value={val.nlc}
/>
<br />
<input
type="text"
readonly
autocomplete="off"
placeholder="UIC"
bind:value={val.uic}
/>
<br />
<button type="submit">Submit</button>
<button type="reset" on:click={reset}>Reset</button>
</form>
</div> </div>
<Nav /> <Nav />
<style> <style>
input { input {
font-family: urwgothic, sans-serif; font-family: urwgothic, sans-serif;
border-radius: 50px; border-radius: 50px;
border: none; border: none;
width: 60%; width: 60%;
max-width: 200px; max-width: 200px;
min-width: 130px; min-width: 130px;
height: 30px; height: 30px;
margin-bottom: 10px; margin-bottom: 10px;
text-transform: uppercase; text-transform: uppercase;
} }
button { button {
border-radius: 50px; border-radius: 50px;
font-family: urwgothic, sans-serif; font-family: urwgothic, sans-serif;
background-color: var(--overlay-color); background-color: var(--overlay-color);
border: none; border: none;
color: white; color: white;
height: 30px; height: 30px;
width: 20%; width: 20%;
max-width: 100px; max-width: 100px;
min-width: 75px; min-width: 75px;
font-size: 18px; font-size: 18px;
} }
.desc { .desc {
color: white; color: white;
} }
input { input {
text-align: center; text-align: center;
} }
</style> </style>

View File

@ -1,60 +1,68 @@
<script> <script>
import Header from '$lib/navigation/header.svelte'; import Header from '$lib/navigation/header.svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import Nav from '$lib/navigation/nav.svelte'; import Nav from '$lib/navigation/nav.svelte';
import { uuid } from '$lib/stores/uuid.js'; import { uuid } from '$lib/stores/uuid.js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
const title = "Your Data"; const title = 'Your Data';
let data = [ let data = [
{ {
"domain": "User not Found", domain: 'User not Found',
"atime": "User not Found" atime: 'User not Found'
}
]
let isLoading = false;
async function fetchData() {
if ($uuid != "null") {
const url = `https://owlboard.info/api/v2/user/${$uuid}`
const res = await fetch(url);
const json = await res.json();
if (json.length) {
data = json
}
}
} }
];
onMount(async () => { let isLoading = false;
isLoading = true;
await fetchData(); async function fetchData() {
isLoading = false; if ($uuid != 'null') {
}) const url = `https://owlboard.info/api/v2/user/${$uuid}`;
const res = await fetch(url);
const json = await res.json();
if (json.length) {
data = json;
}
}
}
onMount(async () => {
isLoading = true;
await fetchData();
isLoading = false;
});
</script> </script>
<Header {title} /> <Header {title} />
<p>OwlBoard stores as little data about you as possible to offer the service.</p> <p>
<p>Your randomly generated UUID is not displayed, this is stored in your browser and on the OwlBoard server.</p> OwlBoard stores as little data about you as possible to offer the service.
<p>The data below is the entirity of the data we hold about you and constitutes a response to a `Subject Access Request` under GDPR legislation.</p> </p>
<br><br> <p>
Your randomly generated UUID is not displayed, this is stored in your browser
and on the OwlBoard server.
</p>
<p>
The data below is the entirity of the data we hold about you and constitutes a
response to a `Subject Access Request` under GDPR legislation.
</p>
<br /><br />
{#if isLoading} {#if isLoading}
<Loading /> <Loading />
{:else if data[0].domain != 'User not Found'}
<p class="api_response">Registration Domain: {data[0]['domain']}</p>
<p class="api_response">Access Time: {data[0]['atime']}</p>
{:else} {:else}
{#if data[0].domain != "User not Found"} <p class="api_response">
<p class="api_response">Registration Domain: {data[0]['domain']}</p> You are not registered, we don't have any data stored.
<p class="api_response">Access Time: {data[0]['atime']}</p> </p>
{:else}
<p class="api_response">You are not registered, we don't have any data stored.</p>
{/if}
{/if} {/if}
<Nav /> <Nav />
<style> <style>
.api_response { .api_response {
color: white; color: white;
} }
</style> </style>

View File

@ -1,41 +1,82 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
const title = "Privacy Policy" const title = 'Privacy Policy';
</script> </script>
<Header {title} /> <Header {title} />
<div> <div>
<p>OwlBoard stores the minimum amount of data necessary to provide its functions for your use. No personal data is stored unless you report an issue. To review the specific data that we store, please visit <a href="/more/data">My Data</a>.</p> <p>
<p>OwlBoard does not utilize any cookies. IP addresses and browser fingerprints are not logged.</p> OwlBoard stores the minimum amount of data necessary to provide its
<h2>If You Do Not Sign Up</h2> functions for your use. No personal data is stored unless you report an
<p>If you choose not to sign up, no personal data will be processed or stored unless you report an issue. Any personal settings are stored locally in your browser and do not leave your device.</p> issue. To review the specific data that we store, please visit <a
<h2>If You Sign Up</h2> href="/more/data">My Data</a
<p>If you sign up for the rail staff version of OwlBoard, we do require the storage of some data. However, none of this data can be used to personally identify you. Any personal settings are stored locally in your browser and do not leave your device.</p> >.
<p>During the sign-up process, you will be asked to provide a work email address, which will be checked to confirm its origin from a railway company. Once confirmed, an email containing a registration link will be sent to you. At this point, the username portion of your email address is discarded. For example, if your email address is 'a-user@owlboard.info', only 'owlboard.info' will be stored. This host portion of your email address is stored to filter and display relevant results prominently.</p> </p>
<p>The email server may store the address and message content as part of its regular operation, and your consent to this is implied when you sign up.</p> <p>
<p>In addition to the host portion of your email address, a randomly generated UUID is stored for the purpose of authorizing access to the rail staff data.</p> OwlBoard does not utilize any cookies. IP addresses and browser fingerprints
<h2>Reporting an Issue</h2> are not logged.
<p>When you report an issue, certain data is collected, including your browser's User Agent string and the size of the window in which you are viewing the website.</p> </p>
<p>Any data submitted when reporting an issue will be publicly viewable alongside the <a href="https://git.fjla.uk/owlboard/backend/issues" target="_blank">OwlBoard/backend git repository</a>.</p> <h2>If You Do Not Sign Up</h2>
</div> <p>
If you choose not to sign up, no personal data will be processed or stored
unless you report an issue. Any personal settings are stored locally in your
browser and do not leave your device.
</p>
<h2>If You Sign Up</h2>
<p>
If you sign up for the rail staff version of OwlBoard, we do require the
storage of some data. However, none of this data can be used to personally
identify you. Any personal settings are stored locally in your browser and
do not leave your device.
</p>
<p>
During the sign-up process, you will be asked to provide a work email
address, which will be checked to confirm its origin from a railway company.
Once confirmed, an email containing a registration link will be sent to you.
At this point, the username portion of your email address is discarded. For
example, if your email address is 'a-user@owlboard.info', only
'owlboard.info' will be stored. This host portion of your email address is
stored to filter and display relevant results prominently.
</p>
<p>
The email server may store the address and message content as part of its
regular operation, and your consent to this is implied when you sign up.
</p>
<p>
In addition to the host portion of your email address, a randomly generated
UUID is stored for the purpose of authorizing access to the rail staff data.
</p>
<h2>Reporting an Issue</h2>
<p>
When you report an issue, certain data is collected, including your
browser's User Agent string and the size of the window in which you are
viewing the website.
</p>
<p>
Any data submitted when reporting an issue will be publicly viewable
alongside the <a
href="https://git.fjla.uk/owlboard/backend/issues"
target="_blank">OwlBoard/backend git repository</a
>.
</p>
</div>
<Nav /> <Nav />
<style> <style>
div { div {
text-align: left; text-align: left;
} }
h2 { h2 {
color: var(--second-text-color); color: var(--second-text-color);
margin: 10px; margin: 10px;
padding-top: 20px; padding-top: 20px;
} }
p { p {
color: white; color: white;
margin: 10px; margin: 10px;
} }
</style> </style>

View File

@ -5,49 +5,45 @@
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import ResultIsland from '$lib/islands/result-island.svelte'; import ResultIsland from '$lib/islands/result-island.svelte';
const title = "Reason Codes"; const title = 'Reason Codes';
let isLoading = false; let isLoading = false;
let inputValue = ''; let inputValue = '';
let resultObject = { let resultObject = {
results: false, results: false,
title: "", title: '',
resultLines: [] resultLines: []
} };
function load() { function load() {
isLoading = true; isLoading = true;
resultObject.results = false; resultObject.results = false;
getData().then(result => { getData().then((result) => {
handleData(result); handleData(result);
isLoading = false; isLoading = false;
}); });
} }
async function getData() { async function getData() {
if (inputValue) { if (inputValue) {
const url = `https://owlboard.info/api/v2/ref/reasonCode/${inputValue}`; const url = `https://owlboard.info/api/v2/ref/reasonCode/${inputValue}`;
const res = await fetch(url); const res = await fetch(url);
return await res.json(); return await res.json();
} else { } else {
return [] return [];
} }
} }
async function handleData(data) { async function handleData(data) {
let resultLines = [] let resultLines = [];
if (data.length) { if (data.length) {
resultObject.title = data[0]['code']; resultObject.title = data[0]['code'];
resultLines = [ resultLines = [data[0]['lateReason'], data[0]['cancReason']];
data[0]['lateReason'],
data[0]['cancReason']
]
} else { } else {
resultObject = { resultObject = {
results: false, results: false,
title: "Not Found", title: 'Not Found',
resultLines: [] resultLines: []
} };
} }
resultObject.resultLines = resultLines; resultObject.resultLines = resultLines;
resultObject.results = true; resultObject.results = true;
@ -68,11 +64,20 @@
</script> </script>
<Header {title} /> <Header {title} />
<p>A reason code is a three digit number that maps to a reason for a delay or cancellation</p> <p>
A reason code is a three digit number that maps to a reason for a delay or
cancellation
</p>
<form on:submit={handleSubmit}> <form on:submit={handleSubmit}>
<input type="text" placeholder="Enter Code" autocomplete="off" bind:value={inputValue} on:input={handleInput} /> <input
type="text"
placeholder="Enter Code"
autocomplete="off"
bind:value={inputValue}
on:input={handleInput}
/>
<br> <br />
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
@ -88,34 +93,36 @@
<Nav /> <Nav />
<style> <style>
p { p {
margin-top: 20px; margin-top: 20px;
} }
input { input {
width: 25%; width: 25%;
min-width: 150px; min-width: 150px;
height: 32px; height: 32px;
margin-top: 10px; margin-top: 10px;
margin-bottom: 5px; margin-bottom: 5px;
border-radius: 50px; border-radius: 50px;
border: none; border: none;
text-align: center; text-align: center;
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
text-transform: uppercase; 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
font-size: 15px; text-transform: uppercase;
} font-size: 15px;
button { }
width: 15%; button {
min-width: 100px; width: 15%;
margin-bottom: 5px; min-width: 100px;
margin-top: 5px; margin-bottom: 5px;
border: none; margin-top: 5px;
border-radius: 20px; border: none;
padding: 5px; border-radius: 20px;
font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; padding: 5px;
font-size: 16px; font-family: urwgothic, 'Lucida Sans', 'Lucida Sans Regular',
font-weight: 400; 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
background-color: var(--overlay-color); font-size: 16px;
color: var(--link-color); font-weight: 400;
} background-color: var(--overlay-color);
color: var(--link-color);
}
</style> </style>

View File

@ -1,49 +1,48 @@
<script> <script>
import Header from "$lib/navigation/header.svelte"; import Header from '$lib/navigation/header.svelte';
import Nav from "$lib/navigation/nav.svelte"; import Nav from '$lib/navigation/nav.svelte';
import Loading from "$lib/navigation/loading.svelte"; import Loading from '$lib/navigation/loading.svelte';
import { onMount } from "svelte"; import { onMount } from 'svelte';
import { uuid } from "$lib/stores/uuid.js"; import { uuid } from '$lib/stores/uuid.js';
const title = "Register"; const title = 'Register';
let state = "unreg"; let state = 'unreg';
let isLoading = false; let isLoading = false;
let inputValue = ""; let inputValue = '';
function handleInput(event) { function handleInput(event) {
inputValue = event.target.value; inputValue = event.target.value;
}
async function request() {
isLoading = true;
const url = 'https://owlboard.info/api/v2/user/request';
const request = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: inputValue
})
};
const res = await fetch(url, request);
if (res.status == 400 || res.status == 403) {
state = 'unauth';
} else if (res.status == 201) {
state = 'sent';
} else {
state = 'error';
} }
isLoading = false;
}
async function request() { onMount(async () => {
isLoading = true; if ($uuid != 'null') {
const url = 'https://owlboard.info/api/v2/user/request'; state = 'reg';
const request = {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email: inputValue
})
}
const res = await fetch(url, request);
if (res.status == 400 || res.status == 403) {
state = "unauth";
} else if (res.status == 201) {
state = "sent";
} else {
state = "error";
}
isLoading = false;
} }
});
onMount(async () => {
if ($uuid != "null") {
state = "reg"
}
})
</script> </script>
{#if isLoading} {#if isLoading}
@ -51,71 +50,95 @@
{/if} {/if}
<Header {title} /> <Header {title} />
{#if state == "unreg"} {#if state == 'unreg'}
<p>The staff version of OwlBoard offers several extra features:</p> <p>The staff version of OwlBoard offers several extra features:</p>
<ul> <ul>
<li>Access the Train Finder</li> <li>Access the Train Finder</li>
<li>Access the PIS Finder</li> <li>Access the PIS Finder</li>
<li>More detailed departure boards:</li> <li>More detailed departure boards:</li>
<ul> <ul>
<li>Non-Passenger movements</li> <li>Non-Passenger movements</li>
<li>Hidden platform numbers</li> <li>Hidden platform numbers</li>
<li>Display up to 40 services</li> <li>Display up to 40 services</li>
</ul> </ul>
</ul> </ul>
<p>To register, you will need to enter a work email address to receive a confirmation email</p> <p>
To register, you will need to enter a work email address to receive a
confirmation email
</p>
<form on:submit={request}> <form on:submit={request}>
<input type="text" autocomplete="email" placeholder="Enter work email" bind:value={inputValue} on:input={handleInput}><br> <input
type="text"
autocomplete="email"
placeholder="Enter work email"
bind:value={inputValue}
on:input={handleInput}
/><br />
<label for="checkbox"> <label for="checkbox">
I have read and accept the terms of the <a href="/more/privacy">Privacy Policy</a><br> I have read and accept the terms of the <a href="/more/privacy"
<input id="checkbox" type="checkbox" required> >Privacy Policy</a
</label><br> ><br />
<input id="checkbox" type="checkbox" required />
</label><br />
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
{:else if state == 'sent'}
{:else if state == "sent"} <p>
<p>An email has been sent, click the link in the email to activate your profile.</p> An email has been sent, click the link in the email to activate your
<p>When you click the link, your authorisation key will be automatically be stored in your browser.</p> profile.
<p>If you use multiple browsers, you will only be logged in using the browser you open the link with.</p> </p>
<p>
When you click the link, your authorisation key will be automatically be
stored in your browser.
</p>
<p>
If you use multiple browsers, you will only be logged in using the browser
you open the link with.
</p>
<p>You will be able to register again using the same email address</p> <p>You will be able to register again using the same email address</p>
{:else if state == "unauth"} {:else if state == 'unauth'}
<p>The email address you entered does not belong to an authorised business.</p> <p>
<p>If you think this is an error, you can report an issue in the 'More' menu.</p> The email address you entered does not belong to an authorised business.
{:else if state == "error"} </p>
<p>
If you think this is an error, you can report an issue in the 'More' menu.
</p>
{:else if state == 'error'}
<p>There was an error processing your request.</p> <p>There was an error processing your request.</p>
<p>Check that the email you entered was correct or try again later.</p> <p>Check that the email you entered was correct or try again later.</p>
{:else if state == "reg"} {:else if state == 'reg'}
<p>You are already registered for OwlBoard</p> <p>You are already registered for OwlBoard</p>
{/if} {/if}
<Nav /> <Nav />
<style> <style>
ul { ul {
text-align: left; text-align: left;
color: white; color: white;
} }
input { input {
height: 40px; height: 40px;
width: 80%; width: 80%;
max-width: 375px; max-width: 375px;
font-family: urwgothic, 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif; font-family: urwgothic, 'Franklin Gothic Medium', 'Arial Narrow', Arial,
text-align: center; sans-serif;
font-size: 20px; text-align: center;
border-radius: 50px; font-size: 20px;
border: none; border-radius: 50px;
margin-bottom: 20px; border: none;
} margin-bottom: 20px;
#checkbox { }
height: 30px; #checkbox {
width: 30px; height: 30px;
} width: 30px;
button { }
border: none; button {
background-color: var(--overlay-color); border: none;
color: white; background-color: var(--overlay-color);
width: 35%; color: white;
height: 30px; width: 35%;
border-radius: 50px; height: 30px;
font-size: 18px; border-radius: 50px;
} font-size: 18px;
}
</style> </style>

View File

@ -1,72 +1,73 @@
<script> <script>
import Header from "$lib/navigation/header.svelte"; import Header from '$lib/navigation/header.svelte';
import Loading from "$lib/navigation/loading.svelte"; import Loading from '$lib/navigation/loading.svelte';
import Nav from "$lib/navigation/nav.svelte"; import Nav from '$lib/navigation/nav.svelte';
import { uuid } from "$lib/stores/uuid.js"; import { uuid } from '$lib/stores/uuid.js';
import { onMount } from "svelte"; import { onMount } from 'svelte';
const title = "Registration" const title = 'Registration';
let state = ""; let state = '';
let isLoading = true let isLoading = true;
async function getUUID() { async function getUUID() {
return new URLSearchParams(window.location.search).get('key'); return new URLSearchParams(window.location.search).get('key');
}
async function submit(id) {
const url = 'https://owlboard.info/api/v2/user/register';
const request = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uuid: id
})
};
const res = await fetch(url, request);
const body = await res.json();
if (body.api_key) {
uuid.set(body.api_key);
return 201;
} }
return res.status;
}
async function submit(id) { onMount(async () => {
const url = "https://owlboard.info/api/v2/user/register" const id = (await getUUID()) || '';
const request = { if (id == '' || !id) {
method: "POST", state = 'none';
headers: { isLoading = false;
"Content-Type": "application/json" return;
},
body: JSON.stringify({
uuid: id
})
}
const res = await fetch(url, request);
const body = await res.json();
if (body.api_key) {
uuid.set(body.api_key);
return 201
}
return res.status;
} }
const status = await submit(id);
onMount(async () => { if (status == 201) {
const id = await getUUID() || ""; console.log('Registered Successfully');
if (id == "" || !id) { state = 'done';
state = "none" } else if (status == 401) {
isLoading = false console.log('Invalid Key');
return state = 'unauth';
} } else {
const status = await submit(id) console.log('Error');
if (status == 201) { state = 'error';
console.log("Registered Successfully") }
state = "done" isLoading = false;
} else if (status == 401) { });
console.log("Invalid Key")
state = "unauth"
} else {
console.log("Error")
state = "error"
}
isLoading = false
})
</script> </script>
<Header {title} /> <Header {title} />
{#if isLoading} {#if isLoading}
<Loading /> <Loading />
{/if} {/if}
{#if state == "none"} {#if state == 'none'}
<p>Unable to read your access key.</p> <p>Unable to read your access key.</p>
<p>Please click the link in your email again.</p> <p>Please click the link in your email again.</p>
{:else if state == "unauth"} {:else if state == 'unauth'}
<p>Your link is not valid, links expire after 30 minutes.</p> <p>Your link is not valid, links expire after 30 minutes.</p>
<p>You can try to register again.</p> <p>You can try to register again.</p>
{:else if state == "error"} {:else if state == 'error'}
<p>There was an error on our end, please try again later</p> <p>There was an error on our end, please try again later</p>
{:else if state == "done"} {:else if state == 'done'}
<p>You are now logged in</p> <p>You are now logged in</p>
{/if} {/if}
<Nav /> <Nav />

View File

@ -1,76 +1,80 @@
<script> <script>
import Header from "$lib/navigation/header.svelte"; import Header from '$lib/navigation/header.svelte';
import Island from "$lib/islands/island.svelte"; import Island from '$lib/islands/island.svelte';
import Nav from "$lib/navigation/nav.svelte"; import Nav from '$lib/navigation/nav.svelte';
import { onMount } from "svelte"; import { onMount } from 'svelte';
import Loading from "$lib/navigation/loading.svelte"; import Loading from '$lib/navigation/loading.svelte';
import Done from "$lib/navigation/done.svelte"; import Done from '$lib/navigation/done.svelte';
const title = "Report Issue"; const title = 'Report Issue';
let isLoading = false; let isLoading = false;
let isDone = false; let isDone = false;
let isError = false; let isError = false;
let reportType = "", reportSubject = "", reportMsg = "", reportCollected let reportType = '',
onMount(async () => { reportSubject = '',
reportCollected = { reportMsg = '',
userAgent: navigator.userAgent, reportCollected;
browser: navigator.appName, onMount(async () => {
version: navigator.appVersion, reportCollected = {
platform: navigator.platform, userAgent: navigator.userAgent,
viewport: `${window.innerWidth} x ${window.innerHeight}`, browser: navigator.appName,
version: navigator.appVersion,
platform: navigator.platform,
viewport: `${window.innerWidth} x ${window.innerHeight}`
}; };
}) });
let preFlight = false; let preFlight = false;
async function submit() { async function submit() {
console.log(reportType,reportSubject,reportMsg) console.log(reportType, reportSubject, reportMsg);
preFlight = true; preFlight = true;
} }
async function send() { async function send() {
console.log("SEND DATA REQUESTED") console.log('SEND DATA REQUESTED');
isLoading = true isLoading = true;
const formData = JSON.stringify({ const formData = JSON.stringify({
label: reportType, label: reportType,
subject: reportSubject, subject: reportSubject,
msg: `User Agent: ${reportCollected.userAgent}\n` + msg:
`Browser: ${reportCollected.browser}\n` + `User Agent: ${reportCollected.userAgent}\n` +
`BrowserVersion: ${reportCollected.version}\n` + `Browser: ${reportCollected.browser}\n` +
`Platform: ${reportCollected.platform}\n` + `BrowserVersion: ${reportCollected.version}\n` +
`Viewport: ${reportCollected.viewport}\n\n\n` + `Platform: ${reportCollected.platform}\n` +
`User Message:\n` + `Viewport: ${reportCollected.viewport}\n\n\n` +
`${reportMsg}` `User Message:\n` +
}) `${reportMsg}`
const url = `https://owlboard.info/misc/issue` });
const options = { const url = `https://owlboard.info/misc/issue`;
method: "POST", const options = {
headers: { method: 'POST',
"Content-Type": "application/json" headers: {
}, 'Content-Type': 'application/json'
body: formData },
} body: formData
const res = await fetch(url, options) };
if (res.status == 200) { const res = await fetch(url, options);
isLoading = false; if (res.status == 200) {
isDone = true; isLoading = false;
await new Promise(r => setTimeout(r, 2000)); isDone = true;
window.location.href = "/"; await new Promise((r) => setTimeout(r, 2000));
} else { window.location.href = '/';
isLoading = false; } else {
isError = true; isLoading = false;
} isError = true;
}
async function cancel() {
preFlight = false;
isLoading = false;
isDone = false;
isError = false;
} }
}
async function cancel() {
preFlight = false;
isLoading = false;
isDone = false;
isError = false;
}
</script> </script>
<Header {title} /> <Header {title} />
{#if isLoading} {#if isLoading}
@ -82,88 +86,113 @@
{/if} {/if}
{#if !preFlight && !isDone} {#if !preFlight && !isDone}
<p>Any data that you enter here will be visible publicly <p>
<a href="https://git.fjla.uk/OwlBoard/backend/issues" target="_blank">here</a> Any data that you enter here will be visible publicly
<a href="https://git.fjla.uk/OwlBoard/backend/issues" target="_blank"
>here</a
>
</p>
<p>
You will be shown all of the collected data before the form is submitted.
</p> </p>
<p>You will be shown all of the collected data before the form is submitted.</p>
<form on:submit={submit}> <form on:submit={submit}>
<select class="formInputs" name="type" bind:value={reportType} placeholder="Choose Category"> <select
class="formInputs"
name="type"
bind:value={reportType}
placeholder="Choose Category"
>
<option value="" disabled selected>Choose an Issue Type</option> <option value="" disabled selected>Choose an Issue Type</option>
<option value="bug">Problem</option> <option value="bug">Problem</option>
<option value="enhancement">Feature Request</option> <option value="enhancement">Feature Request</option>
<option value="question">Question</option> <option value="question">Question</option>
<option value="user-support">Unable to sign up</option> <option value="user-support">Unable to sign up</option>
</select> </select>
<br> <br />
<input class="formInputs" type="text" bind:value={reportSubject} placeholder="Subject"> <input
<br> class="formInputs"
<textarea class="formInputs" bind:value={reportMsg} placeholder="Enter your message..."/> type="text"
<br> bind:value={reportSubject}
placeholder="Subject"
/>
<br />
<textarea
class="formInputs"
bind:value={reportMsg}
placeholder="Enter your message..."
/>
<br />
<button type="submit">Submit</button> <button type="submit">Submit</button>
<button type="reset">Reset</button> <button type="reset">Reset</button>
</form> </form>
{:else} {:else}
<Island> <Island>
<h2>Device Data:</h2> <h2>Device Data:</h2>
<p><span class="dataType">User Agent: </span>{reportCollected.userAgent}</p> <p><span class="dataType">User Agent: </span>{reportCollected.userAgent}</p>
<p><span class="dataType">Browser: </span>{reportCollected.browser} - {reportCollected.version}</p> <p>
<p><span class="dataType">Platform: </span>{reportCollected.platform}</p> <span class="dataType">Browser: </span>{reportCollected.browser} - {reportCollected.version}
<p><span class="dataType">Viewport: </span> {reportCollected.viewport}</p> </p>
<p><span class="dataType">Platform: </span>{reportCollected.platform}</p>
<p><span class="dataType">Viewport: </span> {reportCollected.viewport}</p>
<h2>Reported Data:</h2> <h2>Reported Data:</h2>
<p><span class="dataType">Report Type: </span>{reportType}</p> <p><span class="dataType">Report Type: </span>{reportType}</p>
<p>{reportSubject}</p> <p>{reportSubject}</p>
<p>{reportMsg}</p> <p>{reportMsg}</p>
<button class="overlayButtons" on:click={send}>Send</button> <button class="overlayButtons" on:click={send}>Send</button>
<button class="overlayButtons" on:click={cancel}>Cancel</button> <button class="overlayButtons" on:click={cancel}>Cancel</button>
</Island> </Island>
{/if} {/if}
<Nav /> <Nav />
<style> <style>
select { select {
text-align: center; text-align: center;
border: none; border: none;
border-radius: 50px; border-radius: 50px;
height: 30px; height: 30px;
background-color: white; background-color: white;
} }
.formInputs { .formInputs {
margin: 10px; margin: 10px;
font-family: urwgothic, sans-serif; font-family: urwgothic, sans-serif;
font-size: 16px; font-size: 16px;
border: none; border: none;
width: 50%; width: 50%;
max-width: 250px; max-width: 250px;
min-width: 100px; min-width: 100px;
} }
input { input {
text-align: center; text-align: center;
border-radius: 50px; border-radius: 50px;
height: 30px; height: 30px;
} }
textarea { textarea {
text-align: left; text-align: left;
border-radius: 10px; border-radius: 10px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
height: 30vh; height: 30vh;
min-height: 150px; min-height: 150px;
max-height: 400px; max-height: 400px;
} }
button { button {
background-color: var(--overlay-color); background-color: var(--overlay-color);
color: white; color: white;
font-family: urwgothic, sans-serif; font-family: urwgothic, sans-serif;
font-size: 16px; font-size: 16px;
border: none; border: none;
border-radius: 50px; border-radius: 50px;
width: 25%; width: 25%;
height: 30px; height: 30px;
max-width: 100px; max-width: 100px;
} }
h2 {color: white;} h2 {
.dataType {color: white;} color: white;
.overlayButtons { }
background-color: var(--main-bg-color); .dataType {
} color: white;
}
.overlayButtons {
background-color: var(--main-bg-color);
}
</style> </style>

View File

@ -1,13 +1,12 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
import QlSet from '$lib/islands/quick-link-set-island.svelte' import QlSet from '$lib/islands/quick-link-set-island.svelte';
const title = "Settings" const title = 'Settings';
</script> </script>
<Header {title} /> <Header {title} />
<QlSet /> <QlSet />
<Nav /> <Nav />

View File

@ -1,35 +1,34 @@
<script> <script>
import Island from '$lib/islands/island.svelte'; import Island from '$lib/islands/island.svelte';
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
const title = "Statistics" const title = 'Statistics';
let isLoading = true; let isLoading = true;
let data, error; let data, error;
onMount(async () => { onMount(async () => {
const url = "https://owlboard.info/misc/server/stats" const url = 'https://owlboard.info/misc/server/stats';
const res = await fetch(url); const res = await fetch(url);
if (res.status == 200) { if (res.status == 200) {
data = await res.json(); data = await res.json();
} else { } else {
error = true; error = true;
}
isLoading = false;
})
function U2L(input) {
try {
const datetime = new Date(input*1000)
return datetime.toLocaleString()
} catch (err) {
console.log(err);
return false;
}
} }
isLoading = false;
});
function U2L(input) {
try {
const datetime = new Date(input * 1000);
return datetime.toLocaleString();
} catch (err) {
console.log(err);
return false;
}
}
</script> </script>
<Header {title} /> <Header {title} />
@ -43,7 +42,7 @@ import Header from '$lib/navigation/header.svelte'
{#if isLoading} {#if isLoading}
<Loading /> <Loading />
{:else if !isLoading && !error} {:else if !isLoading && !error}
<p>API Server:<br><span>{data?.hostname}</span></p> <p>API Server:<br /><span>{data?.hostname}</span></p>
<p>Runtime Mode: <span>{data?.runtimeMode}</span></p> <p>Runtime Mode: <span>{data?.runtimeMode}</span></p>
<p>Stats Reset: <span>{U2L(data?.reset) || 'Unknown'}</span></p> <p>Stats Reset: <span>{U2L(data?.reset) || 'Unknown'}</span></p>
<h2>Last Update</h2> <h2>Last Update</h2>

View File

@ -1,26 +1,26 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Island from '$lib/islands/island.svelte'; import Island from '$lib/islands/island.svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
import LargeLogo from '$lib/images/large-logo.svelte'; import LargeLogo from '$lib/images/large-logo.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
const title = "Versions" const title = 'Versions';
const variable = {title:""}; const variable = { title: '' };
let data, isLoading = true; let data,
isLoading = true;
async function getData() { async function getData() {
const url = 'https://owlboard.info/misc/server/versions' const url = 'https://owlboard.info/misc/server/versions';
const res = await fetch(url) const res = await fetch(url);
data = await res.json(); data = await res.json();
isLoading = false; isLoading = false;
} }
onMount(() => {
getData();
});
onMount(() => {
getData();
});
</script> </script>
<Header {title} /> <Header {title} />
@ -28,16 +28,20 @@
<LargeLogo /> <LargeLogo />
{#if isLoading} {#if isLoading}
<Loading/> <Loading />
{:else} {:else}
<Island> <Island>
<p>Web-app Version<br><span class="data">{"2023.7.1-Svelte-Dev"}</span></p> <p>
<p>API Server Version<br><span class="data">{data.backend}</span></p> Web-app Version<br /><span class="data">{'2023.7.1-Svelte-Dev'}</span>
<p>DBManager Version<br><span class="data">{data['db-manager']}</span></p> </p>
<p>API Server Version<br /><span class="data">{data.backend}</span></p>
<p>DBManager Version<br /><span class="data">{data['db-manager']}</span></p>
</Island> </Island>
{/if} {/if}
<Nav /> <Nav />
<style> <style>
.data {color: white;} .data {
color: white;
}
</style> </style>

View File

@ -1,73 +1,73 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Nav from '$lib/navigation/nav.svelte' import Nav from '$lib/navigation/nav.svelte';
import Island from '$lib/islands/island.svelte'; import Island from '$lib/islands/island.svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import { uuid } from '$lib/stores/uuid'; import { uuid } from '$lib/stores/uuid';
const title = "PIS Finder" const title = 'PIS Finder';
const variables = {title: "Results"} const variables = { title: 'Results' };
let entryPIS = ""; let entryPIS = '';
let entryStartCRS = ""; let entryStartCRS = '';
let entryEndCRS = ""; let entryEndCRS = '';
let data = []; let data = [];
let error = false; let error = false;
let errMsg = "Unknown Error" let errMsg = 'Unknown Error';
let isLoading = false let isLoading = false;
async function findByStartEnd() { async function findByStartEnd() {
isLoading = true isLoading = true;
const url = `https://owlboard.info/api/v2/pis/byStartEnd/${entryStartCRS}/${entryEndCRS}` const url = `https://owlboard.info/api/v2/pis/byStartEnd/${entryStartCRS}/${entryEndCRS}`;
await fetchData(url); await fetchData(url);
isLoading = false isLoading = false;
}
async function findByPis() {
isLoading = true;
const url = `https://owlboard.info/api/v2/pis/byCode/${entryPIS}`;
await fetchData(url);
isLoading = false;
}
async function fetchData(url) {
const options = {
method: 'GET',
headers: {
uuid: $uuid
}
};
const res = await fetch(url, options); // Enable Auth
if (res.status == 401) {
errMsg = 'You must be logged in to the staff version';
error = true;
return false;
} else if (res.status == 500) {
errMsg = 'Server Error, try again later';
error = true;
return false;
} }
const jsonData = await res.json();
async function findByPis() { if (jsonData.ERROR == 'offline') {
isLoading = true errMsg = 'Connection error, check your internet connection and try again';
const url = `https://owlboard.info/api/v2/pis/byCode/${entryPIS}` error = true;
await fetchData(url); return false;
isLoading = false;
} }
data = jsonData;
}
async function fetchData(url) { async function reset() {
const options = { data = [];
method: "GET", error = false;
headers: { entryPIS = '';
"uuid": $uuid entryStartCRS = '';
} entryEndCRS = '';
} }
const res = await fetch(url, options); // Enable Auth
if (res.status == 401) {
errMsg = "You must be logged in to the staff version"
error = true
return false
} else if (res.status == 500) {
errMsg = "Server Error, try again later"
error = true
return false
}
const jsonData = await res.json();
if (jsonData.ERROR == "offline") {
errMsg = "Connection error, check your internet connection and try again"
error = true
return false
}
data = jsonData;
}
async function reset() {
data = [];
error = false;
entryPIS = "";
entryStartCRS = "";
entryEndCRS = "";
}
</script> </script>
<Header {title} /> <Header {title} />
{#if isLoading} {#if isLoading}
<Loading/> <Loading />
{/if} {/if}
{#if error} {#if error}
@ -75,101 +75,119 @@
<p class="error">{errMsg}</p> <p class="error">{errMsg}</p>
</Island> </Island>
{:else if data.length} {:else if data.length}
<Island {variables}> <Island {variables}>
<table> <table>
<tr> <tr>
<th class="toc">TOC</th> <th class="toc">TOC</th>
<th class="code">Code</th> <th class="code">Code</th>
<th class="stops">Stops</th> <th class="stops">Stops</th>
</tr> </tr>
{#each data as item} {#each data as item}
<tr> <tr>
<td class="toc toc-data">{item.operator || "-"}</td> <td class="toc toc-data">{item.operator || '-'}</td>
<td class="code">{item.code}</td> <td class="code">{item.code}</td>
<td class="stops stops-data">{item.stops.join(" ")}</td> <td class="stops stops-data">{item.stops.join(' ')}</td>
</tr> </tr>
{/each} {/each}
</table> </table>
</Island> </Island>
{:else} {:else}
<p>To search by headcode use the Train Finder on the homepage</p> <p>To search by headcode use the Train Finder on the homepage</p>
<p>This feature is only supported for GWR West & Sleeper services</p> <p>This feature is only supported for GWR West & Sleeper services</p>
<p class="label">Find By Start/End CRS:</p> <p class="label">Find By Start/End CRS:</p>
<form on:submit={findByStartEnd}> <form on:submit={findByStartEnd}>
<input type="text" maxlength="3" autocomplete="off" placeholder="Start" bind:value={entryStartCRS}> <input
<input type="text" maxlength="3" autocomplete="off" placeholder="End" bind:value={entryEndCRS}> type="text"
<br> maxlength="3"
autocomplete="off"
placeholder="Start"
bind:value={entryStartCRS}
/>
<input
type="text"
maxlength="3"
autocomplete="off"
placeholder="End"
bind:value={entryEndCRS}
/>
<br />
<button type="submit">Search</button> <button type="submit">Search</button>
</form> </form>
<p class="label">Find By PIS Code:</p> <p class="label">Find By PIS Code:</p>
<form on:submit={findByPis}> <form on:submit={findByPis}>
<input type="number" max="9999" autocomplete="off" placeholder="PIS" bind:value={entryPIS}> <input
<br> type="number"
<button type="submit">Search</button> max="9999"
autocomplete="off"
placeholder="PIS"
bind:value={entryPIS}
/>
<br />
<button type="submit">Search</button>
</form> </form>
{/if} {/if}
<button id="reset" type="reset" on:click={reset}>Reset</button> <button id="reset" type="reset" on:click={reset}>Reset</button>
<Nav /> <Nav />
<style> <style>
.label { .label {
font-weight: 600; font-weight: 600;
color: var(--main-text-color); color: var(--main-text-color);
} }
input { input {
border: none; border: none;
border-radius: 50px; border-radius: 50px;
font-family: urwgothic, sans-serif; font-family: urwgothic, sans-serif;
text-align: center; text-align: center;
text-transform: uppercase; text-transform: uppercase;
width: 30%; width: 30%;
max-width: 250px; max-width: 250px;
height: 30px; height: 30px;
font-size: 16px; font-size: 16px;
margin-bottom: 15px; margin-bottom: 15px;
} }
button { button {
border: none; border: none;
border-radius: 50px; border-radius: 50px;
font-family: urwgothic, sans-serif; font-family: urwgothic, sans-serif;
width: 25%; width: 25%;
max-width: 175px; max-width: 175px;
margin: 0px; margin: 0px;
margin-left: 10px; margin-left: 10px;
margin-right: 10px; margin-right: 10px;
height: 30px; height: 30px;
background-color: var(--overlay-color); background-color: var(--overlay-color);
color: white; color: white;
font-size: 16px; font-size: 16px;
} }
table { table {
width: 100%; width: 100%;
margin:auto; margin: auto;
color: white; color: white;
} }
td { td {
padding-top: 5px; padding-top: 5px;
padding-bottom: 5px; padding-bottom: 5px;
} }
.toc { .toc {
width: 15%; width: 15%;
} }
.code { .code {
width: 20%; width: 20%;
} }
.toc-data { .toc-data {
text-transform: uppercase; text-transform: uppercase;
} }
.stops-data { .stops-data {
text-align: left; text-align: left;
font-family: firamono, monospace; font-family: firamono, monospace;
text-transform: uppercase; text-transform: uppercase;
} }
.error { .error {
color: white; color: white;
} }
#reset { #reset {
margin:25px; margin: 25px;
} }
</style> </style>

View File

@ -1,80 +1,79 @@
<script> <script>
import Header from '$lib/navigation/header.svelte' import Header from '$lib/navigation/header.svelte';
import Loading from '$lib/navigation/loading.svelte'; import Loading from '$lib/navigation/loading.svelte';
import Island from '$lib/islands/island.svelte'; import Island from '$lib/islands/island.svelte';
import Nav from '$lib/navigation/nav.svelte'; import Nav from '$lib/navigation/nav.svelte';
import { uuid } from '$lib/stores/uuid'; import { uuid } from '$lib/stores/uuid';
import { onMount } from 'svelte' import { onMount } from 'svelte';
import TrainDetail from '$lib/train/train-detail.svelte'; import TrainDetail from '$lib/train/train-detail.svelte';
let title = "Timetable Results" let title = 'Timetable Results';
let id = "" let id = '';
let data = []; let data = [];
let isLoading = true; let isLoading = true;
let error = false; let error = false;
let errMsg = ""; let errMsg = '';
$: { $: {
if (id) { if (id) {
title = id.toUpperCase(); title = id.toUpperCase();
} else { } else {
title = "Querying Timetable" title = 'Querying Timetable';
}
}
async function getHeadcode() {
return new URLSearchParams(window.location.search).get('headcode');
}
onMount(async () => {
isLoading = true;
id = (await getHeadcode()) || '';
const res = await fetchData(id);
if (res) {
data = res;
if (!data.length) {
error = true;
errMsg = 'No services found';
} }
} }
isLoading = false;
});
async function getHeadcode() { async function fetchData(id = '') {
return new URLSearchParams(window.location.search).get('headcode'); const date = 'now';
const searchType = 'headcode';
const options = {
method: 'GET',
headers: {
uuid: $uuid
}
};
const url = `https://owlboard.info/api/v2/timetable/train/${date}/${searchType}/${id}`;
const res = await fetch(url, options);
isLoading = false;
if (res.status == 200) {
return await res.json();
} else if (res.status === 401) {
error = true;
errMsg = 'You must be logged into the staff version for this feature';
return false;
} else {
error = true;
errMsg = 'Unable to connect, check your connection and try again';
return false;
} }
}
onMount(async () => {
isLoading = true;
id = await getHeadcode() || "";
const res = await fetchData(id);
if (res) {
data = res;
if (!data.length) {
error = true;
errMsg = "No services found"
}
}
isLoading = false;
})
async function fetchData(id = "") {
const date = 'now';
const searchType = 'headcode'
const options = {
method: "GET",
headers: {
"uuid": $uuid
}
}
const url = `https://owlboard.info/api/v2/timetable/train/${date}/${searchType}/${id}`
const res = await fetch(url, options);
isLoading = false;
if (res.status == 200) {
return await res.json();
} else if (res.status === 401) {
error = true;
errMsg = "You must be logged into the staff version for this feature"
return false;
} else {
error = true;
errMsg = "Unable to connect, check your connection and try again"
return false;
}
}
</script> </script>
<Header {title} /> <Header {title} />
<div id="whitespace"></div> <div id="whitespace" />
{#if error} {#if error}
<Island> <Island>
<p class="error">{errMsg}</p> <p class="error">{errMsg}</p>
</Island> </Island>
{/if} {/if}
{#if isLoading} {#if isLoading}

View File

@ -4,47 +4,45 @@ import { build, files, version } from '$service-worker';
const cacheName = `ob-${version}`; const cacheName = `ob-${version}`;
const assets = [ const assets = [...build, ...files, '/service-worker.js'];
...build,
...files,
"/service-worker.js"
];
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
async function addToCache() { async function addToCache() {
const cache = await caches.open(cacheName); const cache = await caches.open(cacheName);
await cache.addAll(assets); await cache.addAll(assets);
} }
event.waitUntil(addToCache()); event.waitUntil(addToCache());
}) });
self.addEventListener('activate', (event) => { self.addEventListener('activate', (event) => {
async function deleteOldCache() { async function deleteOldCache() {
for (const key of await caches.keys()) { for (const key of await caches.keys()) {
if (key !== cacheName) { if (key !== cacheName) {
await caches.delete(key); await caches.delete(key);
} }
}
} }
}
event.waitUntil(deleteOldCache()); event.waitUntil(deleteOldCache());
}) });
self.addEventListener('fetch', (event) => { self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') {return} if (event.request.method !== 'GET') {
async function respond() { return;
const cacheRes = await caches.match(event.request, {ignoreSearch: true}); }
if (cacheRes) { async function respond() {
return cacheRes; const cacheRes = await caches.match(event.request, { ignoreSearch: true });
} if (cacheRes) {
return cacheRes;
try {
return await fetch(event.request)
} catch (err) {
return {"error": "OFFLINE", "errorMsg": "You are not online"};
}
} }
event.respondWith(respond()); try {
}) return await fetch(event.request);
} catch (err) {
return { error: 'OFFLINE', errorMsg: 'You are not online' };
}
}
event.respondWith(respond());
});

View File

@ -1,33 +1,33 @@
{ {
"name": "OwlBoard", "name": "OwlBoard",
"short_name": "OwlBoard", "short_name": "OwlBoard",
"start_url": "/", "start_url": "/",
"scope": "/", "scope": "/",
"display": "standalone", "display": "standalone",
"background_color": "#404c55", "background_color": "#404c55",
"description": "Live station departures, PIS Code Lookup, Reference Data, Timetable search, with enhanced data for staff.", "description": "Live station departures, PIS Code Lookup, Reference Data, Timetable search, with enhanced data for staff.",
"categories": "travel,utilities", "categories": "travel,utilities",
"lang": "en", "lang": "en",
"orientation": "portrait", "orientation": "portrait",
"theme_color": "#00b7b7", "theme_color": "#00b7b7",
"icons": [ "icons": [
{ {
"src": "/images/app-icons/maskable/mask-icon.svg", "src": "/images/app-icons/maskable/mask-icon.svg",
"sizes": "any", "sizes": "any",
"type": "image/svg+xml", "type": "image/svg+xml",
"purpose": "maskable" "purpose": "maskable"
}, },
{ {
"src": "/images/app-icons/any/plain-logo.svg", "src": "/images/app-icons/any/plain-logo.svg",
"sizes": "any", "sizes": "any",
"type": "image/svg+xml", "type": "image/svg+xml",
"purpose": "any" "purpose": "any"
}, },
{ {
"src": "/images/app-icons/any/plain-logo-512.png", "src": "/images/app-icons/any/plain-logo-512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "any" "purpose": "any"
} }
] ]
} }

View File

@ -1,13 +1,13 @@
import adapter from '@sveltejs/adapter-static'; import adapter from '@sveltejs/adapter-static';
export default { export default {
kit: { kit: {
adapter: adapter({ adapter: adapter({
pages: 'build', pages: 'build',
assets: 'build', assets: 'build',
fallback: 'index.html', fallback: 'index.html',
precompress: true, precompress: true,
strict: true strict: true
}) })
} }
}; };

View File

@ -2,5 +2,5 @@ import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()] plugins: [sveltekit()]
}); });