diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9a715f5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.dockerignore +Dockerfile \ No newline at end of file diff --git a/404.html b/404.html new file mode 100644 index 0000000..0ba9f97 --- /dev/null +++ b/404.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + OwlBoard - Error + + +
+ + + Back + +
+ + + + + OwlBoard Logo + +

Oh no!

+

That page cannot be found

+

Try going to the homepage

+

Error number: 404

+ + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..91b32d1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM fedora:latest as compressor +RUN dnf install brotli nodejs npm jq -y +RUN npm i uglifyjs-folder uglifycss html-minifier-terser -g +COPY . /data/in +RUN bash /data/in/conf/deploy.sh + +FROM fholzer/nginx-brotli:latest +RUN rm /etc/nginx/nginx.conf +RUN apk update +RUN apk add --upgrade libxml2 libxslt +COPY ./conf/nginx.conf /etc/nginx/nginx.conf +COPY --from=compressor /data/out/ /site-static/ \ No newline at end of file diff --git a/board.html b/board.html new file mode 100644 index 0000000..76bdb2a --- /dev/null +++ b/board.html @@ -0,0 +1,129 @@ + + + + + + + + + + OwlBoard - Loading + + + + + + + + + + +
+
+
+

\nLoading

+
+ +
+ + +
+
+ + + + +

+ +
+
+
+
+ +
+ + + + + + + + + + + +
Train Services
OriginDest.Plat.Sch Arr.Exp Arr.Sch Dep.Exp Dep.
+
+ +
+

There are no scheduled train services from this station

+
+ +
+ + + + + + + + + + + +
Ferry Services
OriginDest.Sch Arr.Exp Arr.Sch Dep.Exp Dep.
+
+ +
+ + + + + + + + + + + +
Bus Services
OriginDest.Sch Arr.Exp Arr.Sch Dep.Exp Dep.
+
+ +
+

Oops

+

There was an error with your request

+

The station you are searching for cannot be found

+

The station has no data. It may not be in operation yet/anymore.

+

Connection Error, check your data connection. Retrying.

+
+ + +
+ + \ No newline at end of file diff --git a/conf/deploy.sh b/conf/deploy.sh new file mode 100644 index 0000000..a0d7af1 --- /dev/null +++ b/conf/deploy.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +ROOTIN="/data/in" +ROOTOUT="/data/out" + +echo "Running UglifyJS on /data/in folder" +uglifyjs-folder "$ROOTIN" -x ".js" -eo "$ROOTOUT" + +echo "Running UglifyCSS" +CSSIN="/data/in/styles/" +CSSOUT="/data/out/styles" + +cd $CSSIN +echo "Changed directory" +pwd +for f in * +do + if [ -f "$f" ]; then + uglifycss "$f" --output "$f"; + fi +done + +echo "Moving 'styles' to 'out'" +cp -r /data/in/styles /data/out/styles + +echo "Running html-minifier-terser on /folder" +HTMLIN="/data/in/" +HTMLOUT="/data/out" +html-minifier-terser --collapse-whitespace --remove-comments --file-ext html --input-dir /data/in/ --output-dir /data/out/ + +echo "Moving JSON Manifest file from root to output" +cat /data/in/manifest.json | jq -c > /data/out/manifest.json + +echo "Moving images folder from in/ to out/" +cp -r /data/in/images /data/out/images + +echo "Running GZIP & Brotli on all HTML, JS, CSS, JSON & SVG files" +find /data/out -type f -name \*.html -or -name \*.js -or -name \*.css -or -name \*.json -or -name \*.svg -or -name \*.ttf | while read file; do gzip -k -9 $file; brotli -k -q 11 $file; done \ No newline at end of file diff --git a/conf/nginx.conf b/conf/nginx.conf new file mode 100644 index 0000000..6adc241 --- /dev/null +++ b/conf/nginx.conf @@ -0,0 +1,60 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log notice; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + sendfile on; + keepalive_timeout 65; + + proxy_cache_path /var/cache/nginx keys_zone=owl_cache:20m inactive=24h; + + server { + listen 80; + server_name localhost; + proxy_cache owl_cache; + + add_header Content-Security-Policy "default-src 'self'"; + + location / { + root /site-static/; + index index.html; + gzip_static on; + brotli_static on; + error_page 404 /404.html; + expires 3600; + add_header Cache-Control "public, no-transform"; + } + + location /api/ { + proxy_pass http://localhost:8460; + proxy_cache_key $scheme://$host$uri$is_args$query_string; + proxy_ignore_headers Cache-Control; + proxy_cache_valid 200 2m; # Evaluate whether 2m or 1m is more appropriate + expires 2m; + add_header Cache-Control "private, no-transform"; + } + + location /api/v1/list/ { + proxy_pass http://localhost:8460; + proxy_cache_key $scheme://$host$uri$is_args$query_string; + proxy_ignore_headers Cache-Control; + proxy_cache_valid 200 10080m; + expires 3d; + add_header Cache-Control "public, no-transform"; + } + } +} diff --git a/conn-err.html b/conn-err.html new file mode 100644 index 0000000..ab9797d --- /dev/null +++ b/conn-err.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + OwlBoard - Error + + +
+ + + Close menu + +
+ + + + + OwlBoard Logo + +

Oh no!

+

OwlBoard has encountered a Connection Error

+

Check your data connection and try again

+

Go to the homepage

+
+

Error Code: CERR

+ + \ No newline at end of file diff --git a/find-code.html b/find-code.html new file mode 100644 index 0000000..c1167f2 --- /dev/null +++ b/find-code.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + OwlBoard - Code Lookup + + + + + + +
+ + + + Home + + +
+ + + + + + OwlBoard Logo + +

Code Lookup

+

Enter one known code in the relevant box below and hit submit. + Where they exist, the other code types will be filled in.

+

You cannot yet lookup by location name as the values are not unique.

+

Location name search will be added in the future.

+ +
+
+
+

Searching

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 0000000..d52d5d2 --- /dev/null +++ b/help.html @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + OwlBoard + + + + +
+ + + + Home + + +
+ + + + + + OwlBoard Logo + +

Help

+

OwlBoard gives you quick and easy access to departure boards for + all National Rail stations in the UK.

+

Just type a CRS, TIPLOC or STANOX into the textbox on the homepage and tap + enter on the screen or your keypad. You can also select a differnt board type, + more details on your choices below.

+

For example, Portway Park & + Ride's CRS is 'PRI', and its TIPLOC is 'PTWYPR'; Portsmouth Harbour's + CRS is 'PMH', and its TIPLOC is 'PHBR'.

+

A CRS is always three letters, + a TIPLOC can be between 4-7 letters.

+
+

Don't know the CRS or TIPLOC?

+

Sorry, you can't search by name but you can use our + Code Lookup page to help.

+

Board Types

+

Basic Board - Default

+

The basic board shows the next 10 train arrival and departures, as well as + bus and ferry departures where available.

+

You can tap on a trains origin or destination to see service details.

+
+

Glossary

+

Some of the terms may be new to you or different from those commonly used.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TermDefinition
CANCancelled
CRSComputer Reservation System Code - correctly termed as '3ALPHA'
NLCNational Location Code - Used for finance & accounting
RTRight rime (On time)
STANOXStation Number
TIPLOCTiming Point Location (Name)
+
+

Spotted an issue with the site?

+

Let me know by reporting an issue.

+ + \ No newline at end of file diff --git a/images/app-icons/any/apple-192.png b/images/app-icons/any/apple-192.png new file mode 100644 index 0000000..4f35e3e Binary files /dev/null and b/images/app-icons/any/apple-192.png differ diff --git a/images/app-icons/any/plain-logo-512.png b/images/app-icons/any/plain-logo-512.png new file mode 100644 index 0000000..a0ce8f7 Binary files /dev/null and b/images/app-icons/any/plain-logo-512.png differ diff --git a/images/app-icons/any/plain-logo.svg b/images/app-icons/any/plain-logo.svg new file mode 100644 index 0000000..0fc00f4 --- /dev/null +++ b/images/app-icons/any/plain-logo.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/images/app-icons/maskable/mask-icon.svg b/images/app-icons/maskable/mask-icon.svg new file mode 100644 index 0000000..d3b4411 --- /dev/null +++ b/images/app-icons/maskable/mask-icon.svg @@ -0,0 +1,2 @@ + +image/svg+xml \ No newline at end of file diff --git a/images/icon.svg b/images/icon.svg new file mode 100644 index 0000000..50842f5 --- /dev/null +++ b/images/icon.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/images/logo/mono-logo-33.png b/images/logo/mono-logo-33.png new file mode 100644 index 0000000..c020dc2 Binary files /dev/null and b/images/logo/mono-logo-33.png differ diff --git a/images/logo/mono-logo.svg b/images/logo/mono-logo.svg new file mode 100644 index 0000000..cbf8df6 --- /dev/null +++ b/images/logo/mono-logo.svg @@ -0,0 +1,2 @@ + +image/svg+xml \ No newline at end of file diff --git a/images/logo/square-logo-100.png b/images/logo/square-logo-100.png new file mode 100644 index 0000000..ce1a8b6 Binary files /dev/null and b/images/logo/square-logo-100.png differ diff --git a/images/logo/square-logo.svg b/images/logo/square-logo.svg new file mode 100644 index 0000000..b343391 --- /dev/null +++ b/images/logo/square-logo.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/images/logo/wide_logo.svg b/images/logo/wide_logo.svg new file mode 100644 index 0000000..8fcf373 --- /dev/null +++ b/images/logo/wide_logo.svg @@ -0,0 +1,2 @@ + +image/svg+xml \ No newline at end of file diff --git a/images/logo/wide_logo_200.png b/images/logo/wide_logo_200.png new file mode 100644 index 0000000..1379f67 Binary files /dev/null and b/images/logo/wide_logo_200.png differ diff --git a/images/logo/wide_logo_250.png b/images/logo/wide_logo_250.png new file mode 100644 index 0000000..b2e39dc Binary files /dev/null and b/images/logo/wide_logo_250.png differ diff --git a/images/nav/alert_icon-50.png b/images/nav/alert_icon-50.png new file mode 100644 index 0000000..fac429e Binary files /dev/null and b/images/nav/alert_icon-50.png differ diff --git a/images/nav/alert_icon.svg b/images/nav/alert_icon.svg new file mode 100644 index 0000000..68b5777 --- /dev/null +++ b/images/nav/alert_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + +image/svg+xmlOpenclipartWarning Notification2007-02-08T17:08:47Beveled yellow caution signhttp://openclipart.org/detail/3130/warning-notification-by-eastshoreseastshoresalertcautionclip artcliparticonimagemediapublic domainsvgwarning diff --git a/images/nav/back-40.png b/images/nav/back-40.png new file mode 100644 index 0000000..c4e2c98 Binary files /dev/null and b/images/nav/back-40.png differ diff --git a/images/nav/back.svg b/images/nav/back.svg new file mode 100644 index 0000000..c373b5d --- /dev/null +++ b/images/nav/back.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/images/nav/close-40.png b/images/nav/close-40.png new file mode 100644 index 0000000..a2d6972 Binary files /dev/null and b/images/nav/close-40.png differ diff --git a/images/nav/close.svg b/images/nav/close.svg new file mode 100644 index 0000000..8f346b8 --- /dev/null +++ b/images/nav/close.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/images/nav/hamburger.svg b/images/nav/hamburger.svg new file mode 100644 index 0000000..662bca2 --- /dev/null +++ b/images/nav/hamburger.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/images/nav/home_icon-25.png b/images/nav/home_icon-25.png new file mode 100644 index 0000000..67cfee5 Binary files /dev/null and b/images/nav/home_icon-25.png differ diff --git a/images/nav/home_icon.svg b/images/nav/home_icon.svg new file mode 100644 index 0000000..fc1674d --- /dev/null +++ b/images/nav/home_icon.svg @@ -0,0 +1,15 @@ + + + + + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/images/nav/save-59.png b/images/nav/save-59.png new file mode 100644 index 0000000..9f222df Binary files /dev/null and b/images/nav/save-59.png differ diff --git a/images/nav/save.svg b/images/nav/save.svg new file mode 100644 index 0000000..c96b50d --- /dev/null +++ b/images/nav/save.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/images/nre/nre-powered.xcf b/images/nre/nre-powered.xcf new file mode 100644 index 0000000..47319a7 Binary files /dev/null and b/images/nre/nre-powered.xcf differ diff --git a/images/nre/nre-powered_200w.jxl b/images/nre/nre-powered_200w.jxl new file mode 100644 index 0000000..f86a24c Binary files /dev/null and b/images/nre/nre-powered_200w.jxl differ diff --git a/images/nre/nre-powered_200w.png b/images/nre/nre-powered_200w.png new file mode 100644 index 0000000..b1c7abb Binary files /dev/null and b/images/nre/nre-powered_200w.png differ diff --git a/images/nre/nre-powered_200w.webp b/images/nre/nre-powered_200w.webp new file mode 100644 index 0000000..36595dc Binary files /dev/null and b/images/nre/nre-powered_200w.webp differ diff --git a/images/nre/nre-powered_400w.jxl b/images/nre/nre-powered_400w.jxl new file mode 100644 index 0000000..5a23645 Binary files /dev/null and b/images/nre/nre-powered_400w.jxl differ diff --git a/images/nre/nre-powered_400w.png b/images/nre/nre-powered_400w.png new file mode 100644 index 0000000..ac90a79 Binary files /dev/null and b/images/nre/nre-powered_400w.png differ diff --git a/images/nre/nre-powered_400w.webp b/images/nre/nre-powered_400w.webp new file mode 100644 index 0000000..73abfea Binary files /dev/null and b/images/nre/nre-powered_400w.webp differ diff --git a/images/nre/nre-powered_800w.jxl b/images/nre/nre-powered_800w.jxl new file mode 100644 index 0000000..8fd3636 Binary files /dev/null and b/images/nre/nre-powered_800w.jxl differ diff --git a/images/nre/nre-powered_800w.png b/images/nre/nre-powered_800w.png new file mode 100644 index 0000000..1fe153a Binary files /dev/null and b/images/nre/nre-powered_800w.png differ diff --git a/images/nre/nre-powered_800w.webp b/images/nre/nre-powered_800w.webp new file mode 100644 index 0000000..ede2be2 Binary files /dev/null and b/images/nre/nre-powered_800w.webp differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..f41e44a --- /dev/null +++ b/index.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + OwlBoard + + + +
+
+
+

Loading

+
+ + +
+ + + Open menu + + + + Close menu + +
+ + + + + + + + OwlBoard Logo + +
+
+ +
+ +
+ +

Quick Links

+ +
+

Customise your quick links on the Settings page.

+
+ + + + diff --git a/issue.html b/issue.html new file mode 100644 index 0000000..c59c79c --- /dev/null +++ b/issue.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + OwlBoard - Report + + + +
+
+
+

Loading

+
+
+ + + + Home + + +
+ + + + + OwlBoard Logo + +

Report an Issue

+

To help diagnosing an issue, data about your browser and device will be + collected alongside the data that you enter below.

+

The data will be available publically in the + OwlBoard Issue Tracker. A preview will be shown before the data is sent.

+
+
+
+
+ +
+

Check & Send

+

---

+

+

+ + +
+ + \ No newline at end of file diff --git a/js/find-code.js b/js/find-code.js new file mode 100644 index 0000000..915701e --- /dev/null +++ b/js/find-code.js @@ -0,0 +1,97 @@ +hideLoading(); + +async function fetchEntry(){ // This can be condensed + showLoading(); + var name = document.getElementById("name") + var crs = document.getElementById("3alpha") + var nlc = document.getElementById("nlc") + var tiploc = document.getElementById("tiploc") + var stanox = document.getElementById("stanox") + + var values = { + name: name.value, + crs: crs.value, + nlc: nlc.value, + tiploc: tiploc.value, + stanox: stanox.value + } + parseData(values) +} + +async function parseData(values){ + vibe() + if (values.crs != ""){ + setLoadingDesc(`Searching\n${values.crs.toUpperCase()}`) + var data = await getData("crs", values.crs) + } else if (values.nlc != ""){ + setLoadingDesc(`Searching\n${values.nlc.toUpperCase()}`) + var data = await getData("nlc", values.nlc) + } else if (values.tiploc != ""){ + setLoadingDesc(`Searching\n${values.tiploc.toUpperCase()}`) + var data = await getData("tiploc", values.tiploc) + } else if (values.stanox != ""){ + setLoadingDesc(`Searching\n${values.stanox.toUpperCase()}`) + var data = await getData("stanox", values.stanox) + } else if (values.name != ""){ + setLoadingDesc(`Searching\n${values.name}`) + var data = await getData("name", values.name) + } else { + log("find-code.parseData: No data entered", "WARN") + await clearForm(); + document.getElementById("name").value = "No data entered" + vibe("err"); + hideLoading(); + return; + } + displayData(data); +} + +async function getData(type, value){ + log(`find-code.getData: Looking for: ${type} '${value}'`, "INFO") + try { + var url = `${window.location.origin}/api/v1/find/${type}/${value}`; + var resp = await fetch(url); + return await resp.json() + } catch (err) { + log(`find-code.getData: Error getting data: ${err}`, "WARN") + vibe("err") + return ""; + } +} + +async function displayData(data){ + hideLoading(); + if (data.status === "failed" || data == ""){ + log(`find-code.displayData: Unable to find data`, "WARN") + clearForm(); + document.getElementById("name").value = "Not Found"; + } else { + log(`find-code.displayData: Inserting data`, "INFO") + vibe("ok") + try { + document.getElementById("name").value = data['0']['NLCDESC'] + } catch (err) {} + try { + document.getElementById("3alpha").value = data['0']['3ALPHA'] + } catch (err) {} + try { + document.getElementById("nlc").value = data['0']['NLC'] + } catch (err) {} + try { + document.getElementById("tiploc").value = data['0']['TIPLOC'] + } catch (err) {} + try { + document.getElementById("stanox").value = data['0']['STANOX'] + } catch (err) {} + } +} + +async function clearForm(){ + document.getElementById("name").value = "" + document.getElementById("3alpha").value = "" + document.getElementById("nlc").value = "" + document.getElementById("tiploc").value = "" + document.getElementById("stanox").value = "" + vibe("ok"); + hideLoading(); +} \ No newline at end of file diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..fba16c3 --- /dev/null +++ b/js/index.js @@ -0,0 +1,26 @@ +// Init: +pageInit(); + +if ("serviceWorker" in navigator) { + navigator.serviceWorker.register("/sw.js"); +} + +async function pageInit() { + await loadQuickLinks(); + hideLoading(); // From lib.main +} + +async function gotoBoard(station){ + vibe("ok") + window.location.assign(`${window.location.origin}/board.html?stn=${station}`); +} + +async function loadQuickLinks(){ + var data = await getQuickLinks(); // From lib.main + var buttons = ""; + for(var i = 0; i < data.length; i++) { + buttons += ` + ` + } + document.getElementById("quick_links").insertAdjacentHTML("beforeend", buttons) +} \ No newline at end of file diff --git a/js/issue.js b/js/issue.js new file mode 100644 index 0000000..01d3940 --- /dev/null +++ b/js/issue.js @@ -0,0 +1,88 @@ +init(); + +async function init() { + hideLoading() +} + +async function submit() { + setLoadingDesc("Collecting\nData") + showLoading() + var browserData = await getBrowserData(); + setLoadingDesc("Reading\nForm") + var formData = await getFormData(); + preflight({browserData: browserData, formData: formData}) +} + +async function getFormData() { + let data = {} + data.subject = document.getElementById("subject").value + data.message = document.getElementById("message").value + return data +} + +async function getBrowserData() { + let data = {} + data.userAgent = navigator.userAgent + data.userAgentData = JSON.stringify(navigator.userAgentData) + data.localStorage = JSON.stringify(await storageAvailable('localStorage')) + data.sessionStorage = JSON.stringify(await storageAvailable('sessionStorage')) + data.viewport = `${window.innerWidth} x ${window.innerHeight}` + return data +} + +async function preflight(data) { + document.getElementById("pre_subject").textContent = data.formData.subject + pre_msg = `UserAgent: ${data.browserData.userAgent} + \nUserAgentData: ${data.browserData.userAgentData} + \nlocalStorage Avail: ${data.browserData.localStorage} + \nsessionStorage Avail: ${data.browserData.sessionStorage} + \nViewport size: ${data.browserData.viewport} + \nUser message:\n\n${data.formData.message}` + document.getElementById("pre_message").innerText = pre_msg + hideLoading() + document.getElementById("preflight").style = "display: block" + sessionStorage.setItem("preflight_subject", data.formData.subject) + sessionStorage.setItem("preflight_msg", pre_msg) +} + +async function cancel() { + document.getElementById("preflight").style = "display: none" +} + +async function send() { + setLoadingDesc("Sending\nData") + document.getElementById("preflight").style = "display: none" + showLoading() + var subject = sessionStorage.getItem("preflight_subject"); + var msg = sessionStorage.getItem("preflight_msg") + if (typeof subject != "string") { + subject = document.getElementById("preflight_subject").innerText + } + if (typeof msg != "string") { + msg = document.getElementById("preflight_msg") + } + var payload = JSON.stringify({subject: subject, msg: msg}) + console.log(payload); + let opt = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + redirect: 'follow', + body: payload + } + var res = await fetch(`${window.location.origin}/api/v1/issue`, opt) + if (res.status == 200) { + setLoadingDesc("Success") + vibe("ok") + await delay(2500) + window.location.replace("/") + } else { + setLoadingDesc("Error") + vibe("err") + await delay(2500) + hideLoading() + document.getElementById("preflight").style = "display: none;" + } +} \ No newline at end of file diff --git a/js/lib.board.js b/js/lib.board.js new file mode 100644 index 0000000..e669a93 --- /dev/null +++ b/js/lib.board.js @@ -0,0 +1,248 @@ +/* Fetch Functions */ +async function publicLdb(stn) { + var url = `${window.location.origin}/api/v1/ldb/${stn}`; + var resp = await fetch(url); + return await resp.json(); +} + +/* Set page heading */ +async function setHeaders(title,time) { + var prefix = `OwlBoard - ` + document.title = `${prefix}${title}` + document.getElementById("stn_name").textContent = title + document.getElementById("fetch_time").textContent = time.toLocaleTimeString() + sessionStorage.setItem("board_location", title); +} + +/* Display No Trains Message */ +async function displayNoTrains() { + log("No Trains", "WARN") + document.getElementById('no_services').style = "display: block;"; + hideLoading(); +} + +/* Parse the value of `platform` to account for unknown platforms */ +async function parsePlatform(svc){ + if (svc.platform != undefined) { + var platform = svc.platform; + } else { + var platform = "-"; + } + if (svc.platformChanged) { // Not present in public API, ready for staff version. + var changed = "changed"; + } else { + var changed = ""; + } + return {num: platform, change: changed} +} + + +/* Change value of time strings to fit well on small screens */ +async function parseTime(string){ + switch (string) { + case "Delayed": + var output = "LATE"; + var change = "changed"; + break; + case "Cancelled": + var output = "CANC"; + var change = "cancelled"; + break; + case "On time": + var output = "RT"; + var change = ""; + break; + case "": + var output = "-"; + var change = ""; + break; + case undefined: + var output = "-"; + var change = ""; + break; + case "No report": + var output = "-"; + var change = ""; + break; + case "undefined": + var output = false; + var change = ""; + break; + default: + var output = string; + var change = "changed"; + } + return {data: output, changed: change}; +} + +/* Convert multiple Origin/Destinations to single string */ +async function parseName(location) { + if (Array.isArray(location)) { + var name = `${location[0]['locationName']} & ${location[1]['locationName']}` + return name; + } + else { + return location.locationName; + } +} + +// Display Alert Messages +async function displayAlerts(array) { + var counter = 0 + var messages = "" + for(var i = 0; i < array.length; i++) { + // Increment counter + counter += 1; + // Reset Vars + messages += `

${array[i]}

`; + } + if (counter > 0) { + document.getElementById("alerts_msg").insertAdjacentHTML("beforeend", messages) + document.getElementById("alerts").style = "display:block" + document.getElementById("alerts_bar").style = "display:block" + if (counter == 1) { + document.getElementById("alert_bar_note").textContent = `There is ${counter} active alert` + } else if (counter > 1) { + document.getElementById("alert_bar_note").textContent = `There are ${counter} active alerts` + } + return true; + } + return false; +} + + +/* Show/Hide alerts box */ +async function inflateAlerts() { + document.getElementById("alerts_msg").style = "display:block;"; + document.getElementById("alert_expand_arrow").style = "transform: rotate(180deg);"; + document.getElementById("alerts_bar").setAttribute("onclick", "deflateAlerts()") +} + +async function deflateAlerts() { + document.getElementById("alerts_msg").style = "display.none;"; + document.getElementById("alert_expand_arrow").style = "transform: rotate(0deg);"; + document.getElementById("alerts_bar").setAttribute("onclick", "inflateAlerts()") +} + +/*//// SERVICE DETAIL LISTS ////*/ +// Build calling list: -- This outputs calling point data to sessionStorage in the format: key{pre: [{PREVIOUS_Stops}], post: [{POST_STOPS}]} +async function buildCallLists(svc) { + var sSvcId = svc.serviceID; + var oSvcData = { + plat: svc.platform, + sta: svc.sta, + eta: svc.eta, + std: svc.std, + etd: svc.etd + }; + try { + if (typeof svc.previousCallingPoints.callingPointList.callingPoint != 'undefined') { + let array = await makeArray(svc.previousCallingPoints.callingPointList.callingPoint); + oSvcData.pre = array; + } + } catch (err) { /* Do nothing if ERR */ } + try { + if (typeof svc.subsequentCallingPoints.callingPointList.callingPoint != 'undefined') { + let array = await makeArray(svc.subsequentCallingPoints.callingPointList.callingPoint); + oSvcData.post = array; + } + } catch (err) { /* Do nothing if ERR */ } + sessionStorage.setItem(sSvcId, JSON.stringify(oSvcData)) +} + +/* Display calling list: - Read data from sessionStorage and write to DOM. */ +async function showCalls(id) { + log(`Showing details for service ${id}`, "INFO") + var svcDetail = await JSON.parse(sessionStorage.getItem(id)); + var pre = ""; + var post = ""; + if (typeof svcDetail.pre != 'undefined') { + for(var preCall = 0; preCall < svcDetail.pre.length; preCall++) { + pre += await singleCall(svcDetail.pre[preCall]); + } + } + if (typeof svcDetail.post != 'undefined') { + for(var postCall = 0; postCall < svcDetail.post.length; postCall++) { + post += await singleCall(svcDetail.post[postCall]); + } + } + /* Run retreived data through parsers */ + var thisStd = await parseTime(svcDetail.std); + var thisEtd = await parseTime(svcDetail.etd); + var thisSta = await parseTime(svcDetail.sta); + var thisEta = await parseTime(svcDetail.eta); + /* Prepare data for this station */ + if (thisStd.data != "-") { + var sTime = `${thisStd.data}` + var eTime = `${thisEtd.data}` + var change = thisEtd.changed + } else { + var sTime = `${thisSta.data}` + var eTime = `${thisEta.data}` + var change = thisEta.changed + }; + + let here = ` + ${sessionStorage.getItem("board_location")} + ${sTime} + ${eTime} + ` + /* Prepare then insert DOM Data */ + let dom = `
+

X

+ + + + + + + ${pre} + ${here} + ${post} +
LocationScheduleAct/Est
+
` + + document.body.insertAdjacentHTML("beforeend", dom); + document.getElementById(id).style = "display: block;"; + return; +} + +async function hideCalls(id) { + let element = document.getElementById(id) + element.style = "display: none;"; + element.remove(); + return; +} + +/* Builds the train data information in to a table row */ +async function singleCall(data) { + if (typeof data.et != "undefined") { + var time = await parseTime(data.et) + } else if (typeof data.at != "undefined") { + var time = await parseTime(data.at) + } + return ` + ${data.locationName} + ${data.st} + ${time.data} + ` +} + +/* Error Handler */ +async function errorHandler() { + if (sessionStorage.getItem("failcount")) { + var errCount = parseInt(sessionStorage.getItem("failcount")) + } else { + var errCount = 0; + } + errCount += 1; + sessionStorage.setItem("failcount", errCount.toString()) + if (errCount < 10){ + await delay(3000); + vibe("err") + location.reload() + } else { + sessionStorage.removeItem("failcount"); + window.location.assign("conn-err.html") + } +} \ No newline at end of file diff --git a/js/lib.main.js b/js/lib.main.js new file mode 100644 index 0000000..5968c47 --- /dev/null +++ b/js/lib.main.js @@ -0,0 +1,132 @@ +/* Feature Detectors */ + +/* Valid values for ${type}: localstorage, sessionstorage */ +async function storageAvailable(type) { // Currently not used + try { + let storage = window[type]; + let x = '__storage_test__'; + storage.setItem(x, "test"); + storage.getItem(x); + storage.removeItem(x); + log(`lib.main.storageAvailable: ${type} is available`, "INFO") + return true; + } catch (err) { + log(`lib.main.storageAvailable: ${type} is not available`, "ERR") + return false; + } +} + +/* Array Converter + Converts a string to a single item array */ +async function makeArray(data) { + if (!Array.isArray(data)) { + var array = []; + array.push(data); + return array; + } + return data; +} + +/* Timeouts */ +/* Usage: '' */ +const delay = ms => new Promise(res => setTimeout(res, ms)); + + +/* Log Helper */ +/* Values for level: 1, 2, 3 */ +/* Maintains backwards compatibility for previous + implementation of log helper */ +async function log(msg, type) { + var time = new Date().toISOString(); + switch (type) { + case "ERR": + console.error(`${time} - ${msg}`); + break; + case "WARN": + console.warn(`${time} - ${msg}`); + break; + case "INFO": + console.info(`${time} - ${msg}`); + break; + default: + console.log(`${time} - ${msg}`); + break; + }; +}; + +/* Show/Hide - Menu Control */ +async function sidebarOpen() { + document.getElementById("sidebar").style.width = "50%"; + document.getElementById("sidebar_open_short").style.display = "none"; + document.getElementById("sidebar_close_short").style.display = "block"; + } + + async function sidebarClose() { + document.getElementById("sidebar").style.width = "0%" + document.getElementById("sidebar_open_short").style.display = "block"; + document.getElementById("sidebar_close_short").style.display = "none"; + } + +/* Loading Box Control */ +async function hideLoading() { + document.getElementById("loading").style = "display: none;"; +} + + /* DEPRECIATED: Alias for hideLoading() - Marked for removal*/ + async function clearLoading() { + log("Depreciated function called - clearLoading() - Alias to hideLoading()", "WARN") + await hideLoading(); + } + +async function showLoading() { + document.getElementById("loading").style = "display: block;"; +} + +async function setLoadingDesc(desc) { + document.getElementById("loading_desc").textContent = `${desc}`; +} + +/* Fetch User Settings */ +async function getQuickLinks() { + var defaults = + ["bri","lwh","srd","mtp","rda","cfn", + "sml","shh","pri","avn","sar","svb"]; + try { + if (localStorage.getItem("qlOpt")) { + var data = JSON.parse(localStorage.getItem("qlOpt")); + } else { + data = defaults; + } + } catch (err) { + data = defaults; + } + return data.sort(); +} + +/* Fetch a known query parameter from the pages URL */ +async function getQuery(param) { + var params = new URLSearchParams(window.location.search) + var query = params.get(param) + if (query) { + return query + } else { + return 'false' + } +} + +async function vibe(type) { + let canVibrate = "vibrate" in navigator || "mozVibrate" in navigator + if (canVibrate && !("vibrate" in navigator)){ + navigator.vibrate = navigator.mozVibrate + } + switch (type) { + case "err": + navigator.vibrate([300]) + break; + case "ok": + navigator.vibrate([50,50,50]) + break; + default: + navigator.vibrate(30) + } +} \ No newline at end of file diff --git a/js/settings.js b/js/settings.js new file mode 100644 index 0000000..e053653 --- /dev/null +++ b/js/settings.js @@ -0,0 +1,57 @@ +// Init: +const ql = ["ql0","ql1","ql2","ql3","ql4","ql5","ql6","ql7","ql8","ql9","ql10","ql11"] +storageAvailable("localStorage"); +getQl(); +hideLoading(); + +async function getQl(){ + var qlOpt = await getQuickLinks() + if (qlOpt){ + var i = 0 + while (i < 12) { + if (qlOpt[i] != 'undefined') { + document.getElementById(`ql${i}`).value = qlOpt[i] + i +=1 + } + } + } +} + +async function setQl(){ + await showLoading(); + var qlSet = [] + for (i in ql) { + var opt = document.getElementById(`ql${i}`).value + if (opt != ""){ + qlSet.push(opt) + } + qlSet.sort() + } + localStorage.setItem("qlOpt", JSON.stringify(qlSet)) + log(`settings.setQl: User settings saved`, "INFO") + await hideLoading(); + await showDone(); + vibe("ok") + await delay(800); + hideDone(); +} + +async function clearQl(){ + showLoading(); + localStorage.removeItem("qlOpt") + log(`settings.setQl: User settings reset to default`, "INFO") + getQl() + await hideLoading(); + await showDone(); + vibe("ok"); + await delay(800); + hideDone(); +} + +async function showDone() { + document.getElementById("done").style = "opacity: 1"; +} + +async function hideDone() { + document.getElementById("done").style = "opacity: 0"; +} \ No newline at end of file diff --git a/js/simple-board.js b/js/simple-board.js new file mode 100644 index 0000000..6f48549 --- /dev/null +++ b/js/simple-board.js @@ -0,0 +1,235 @@ +/* Page Init: */ +init() + +/* Init function */ +async function init() { + console.time("Loading Time") + setLoadingDesc(`Loading\nservices`) + var stn = await getQuery("stn"); + setLoadingDesc(`Loading\n${stn.toUpperCase()}`) + log(`init: Looking up: ${stn}`); + var sv = await getQuery("sv"); + log(`init: Staff Version: ${sv}`); + if (sv === 'true') { + log("init: Staff Version not supported yet.") + log("init: Unable to proceed.") + } else { + try { + var data = await publicLdb(stn) + setLoadingDesc(`${stn.toUpperCase()}\nParsing Data`) + log("simple-board.init: Fetched LDB Data", "INFO") + } catch (err) { + var data = "err" + setLoadingDesc(`Waiting\nConnection`) + log(`simple-board.init: Error fetching data: ${err}`, "ERR") + } + parseLdb(data) + } +} + +/* Check for any errors in data returned from the Fetch call + If no errors, if there are none, call buildPage(). */ +async function parseLdb(data) { + if (data.ERROR == "NOT_FOUND") { // Station not found + hideLoading(); + document.getElementById("error_notice").style = "display: block;"; + document.getElementById("err_not_found").style = "display: block;"; + setHeaders("Not Found",new Date()) + } else if (data == false) { // No data for station + hideLoading(); + document.getElementById("error_notice").style = "display: block;"; + document.getElementById("err_no_data").style = "display:block;"; + setHeaders("No Data",new Date()) + } else if (data == "err") { // Connection Error + await delay(2000); + hideLoading(); + document.getElementById("error_notice").style = "display: block;"; + document.getElementById("err_conn").style = "display: block;"; + setHeaders("Connection Error",new Date()) + showLoading(); + await delay(5000); + log(`parseLdb: Passing to error handler`, "ERR") + errorHandler(); + } else { + buildPage(data); + } +} + +// Build and Display Functions +async function buildPage(data) { + setLoadingDesc('Loading\nData') + var stationName = data.GetStationBoardResult.locationName; + log(`buildPage: Data ready for ${stationName}`); + var generateTime = new Date(await data.GetStationBoardResult.generatedAt); + log(`buildPage: Data prepared at ${generateTime.toLocaleString()}`) + setHeaders(stationName, generateTime); + // Check for notices and if true pass to function + if (data.GetStationBoardResult.nrccMessages) { + setLoadingDesc('Loading\nAlerts') + await displayAlerts(await makeArray(data.GetStationBoardResult.nrccMessages.message)); + } + if (data.GetStationBoardResult.trainServices) { + setLoadingDesc('Loading\nTrains') + displayTrains(await makeArray(data.GetStationBoardResult.trainServices.service)) + } else { + displayNoTrains() + } + if (data.GetStationBoardResult.ferryServices) { + setLoadingDesc('Loading\nFerries') + displayFerry(await makeArray(data.GetStationBoardResult.ferryServices.service)) + } + if (data.GetStationBoardResult.busServices) { + setLoadingDesc('Loading\nBusses') + displayBus(await makeArray(data.GetStationBoardResult.busServices.service)) + } + console.timeEnd("Loading Time") +} + + +async function displayTrains(data) { + log(`simple-board.displayTrains: Inserting data in DOM`) + for(var i = 0; i < data.length; i++) { + // Reset Vars + var svc = data[i]; + displayService(svc); + buildCallLists(svc); + } + + hideLoading(); + document.getElementById("output").style = "display:block;"; + log(`simple-board.displayTrains: Insertion complete`) +} + +async function displayFerry(ferrySvc) { + for(var i = 0; i < ferrySvc.length; i++) { + displayFerryService(ferrySvc[i]) + } +} + +async function displayBus(busSvc) { + for(var i = 0; i < busSvc.length; i++) { + displayBusService(busSvc[i]) + buildCallLists(busSvc[i]) + } +} + +async function displayService(svc) { + var table = document.getElementById("output"); + + // Determine Time Message + var sta = await parseTime(svc.sta); + var eta = await parseTime(svc.eta); + var std = await parseTime(svc.std); + var etd = await parseTime(svc.etd); + // Determine Platform Message + //if (svc.platform != undefined){var plt = svc.platform} else {var plt = "-"}; + var plt = await parsePlatform(svc); + // Define Table Row + var row = ` + + + + + + + + + + +
${await parseName(svc.origin.location)}${await parseName(svc.destination.location)}${plt.num}${sta.data}${eta.data}${std.data}${etd.data}
` + // Put Table Row + table.insertAdjacentHTML("beforeend", row) + // Display Operator + if (svc.operator) { + var opRow = `

A ${svc.operator} service

` + table.insertAdjacentHTML("beforeend", opRow); + } + // Parse cancelReason & delayReason + if (svc.cancelReason) { + var cancelRow = `

${svc.cancelReason}

` + table.insertAdjacentHTML("beforeend", cancelRow); + } + if (svc.delayReason) { + var delayRow = `

${svc.delayReason}

` + table.insertAdjacentHTML("beforeend", delayRow); + } +} + +async function displayFerryService(svc) { + var table = document.getElementById("ferry"); + log(JSON.stringify(svc)) + // Determine Time Message + var sta = await parseTime(svc.sta); + var eta = await parseTime(svc.eta); + var std = await parseTime(svc.std); + var etd = await parseTime(svc.etd); + // Determine Platform Message + var plt = ""; + // Define Table Row + var row = ` + + + + + + + + + + +
${await parseName(svc.origin.location)}${await parseName(svc.destination.location)}${plt}${sta.data}${eta.data}${std.data}${etd.data}
` + // Put Table Row + table.insertAdjacentHTML("beforeend", row) + // Parse cancelReason & delayReason + if (svc.cancelReason) { + var cancelRow = `

${svc.cancelReason}

` + table.insertAdjacentHTML("beforeend", cancelRow); + } + if (svc.delayReason) { + var delayRow = `

${svc.delayReason}

` + table.insertAdjacentHTML("beforeend", delayRow); + } + document.getElementById("ferry").style = "display:block" +} + +async function displayBusService(svc) { + var table = document.getElementById("bus"); + log(JSON.stringify(svc)) + // Determine Time Message + var sta = await parseTime(svc.sta); + var eta = await parseTime(svc.eta); + var std = await parseTime(svc.std); + var etd = await parseTime(svc.etd); + // Determine Platform Message + var plt = ""; + // Define Table Row + var row = ` + + + + + + + + + + +
${svc.origin.location.locationName}${svc.destination.location.locationName}${plt}${sta.data}${eta.data}${std.data}${etd.data}
` + // Put Table Row + table.insertAdjacentHTML("beforeend", row) + // Display operator + if (svc.operator) { + var opRow = `

A ${svc.operator} service

` + table.insertAdjacentHTML("beforeend", opRow); + } + // Parse cancelReason & delayReason + if (svc.cancelReason) { + var cancelRow = `

${svc.cancelReason}

` + table.insertAdjacentHTML("beforeend", cancelRow); + } + if (svc.delayReason) { + var delayRow = `

${svc.delayReason}

` + table.insertAdjacentHTML("beforeend", delayRow); + } + document.getElementById("bus").style = "display:block" +} \ No newline at end of file diff --git a/js/stat.js b/js/stat.js new file mode 100644 index 0000000..254ef18 --- /dev/null +++ b/js/stat.js @@ -0,0 +1,24 @@ +init(); + +async function init() { + display(await get()) +} + +async function get() { + var url = `${window.location.origin}/api/v1/stats`; + var resp = await fetch(url); + return await resp.json(); +} + +async function display(data) { + document.getElementById('server_host').textContent = `HOST: ${data.host}`; + let dat = data.dat[0] + console.log(JSON.stringify(dat)) + document.getElementById('time').textContent = dat.since; + document.getElementById('ldbws').textContent = dat.ldbws || "0"; + document.getElementById('ldbsvws').textContent = dat.ldbsvws || "0"; + document.getElementById('corpus').textContent = dat.corpus || "0"; + document.getElementById('stations').textContent = dat.stations || "0"; + document.getElementById('users').textContent = dat.user || "0"; + document.getElementById('meta').textContent = dat.meta || "0"; +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..0666c18 --- /dev/null +++ b/manifest.json @@ -0,0 +1,33 @@ +{ + "name": "OwlBoard", + "short_name": "OwlBoard", + "start_url": "/", + "scope": "/", + "display": "standalone", + "background_color": "#404c55", + "description": "Live station departures - aimed at train-crew.", + "categories": "travel,utilities", + "lang": "en", + "orientation": "portrait", + "theme_color": "#00b7b7", + "icons": [ + { + "src": "/images/app-icons/maskable/mask-icon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "maskable" + }, + { + "src": "/images/app-icons/any/plain-logo.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any" + }, + { + "src": "/images/app-icons/any/plain-logo-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + } + ] +} diff --git a/settings.html b/settings.html new file mode 100644 index 0000000..15cf967 --- /dev/null +++ b/settings.html @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + OwlBoard - Settings + + + + + +
+
+
+

Loading

+
+ +
+ + + + + +

Saved

+
+ +
+ + + + Home + + +
+ + + + + + OwlBoard Logo + +

Settings

+

Any settings you apply will only apply to the device you are using now.

+ +
+

Enter one CRS/3ALPHA code per box

+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + \ No newline at end of file diff --git a/stat.html b/stat.html new file mode 100644 index 0000000..10a262b --- /dev/null +++ b/stat.html @@ -0,0 +1,45 @@ + + + + OwlBoard - STATS + + + +

OwlBoard Server Stats

+

+

Counters Reset -

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ResourceHit Count
LDBWS
LDBSVWS
DB-CORPUS
DB-Stations
DB-Users
DB-Meta
+

+

The statistics represent hits & queries on all servers attached to the database. + Multiple servers are served by each database server.

+ + \ No newline at end of file diff --git a/styles/boards.css b/styles/boards.css new file mode 100644 index 0000000..12c4d34 --- /dev/null +++ b/styles/boards.css @@ -0,0 +1,351 @@ +/* Hide when loading */ +.hide-when-loading { + display: none; +} + +/* Main Notices: */ +.main-notice { + display: none; + margin-top: 150px; +} + +.notices-hidden { + display: none; +} + +#no_services { + width: 75%; + margin: auto; + margin-top: 110px; + margin-bottom: 30px; + font-size: 20px; + font-weight: 900; +} + +/* Fixed Content: */ +#header { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 50px; + background-color: var(--overlay-color); + color: var(--second-text-color); +} + +#station_name { + position: absolute; + max-width: 50%; + left: 7px; + top: 3px; +} + +.header-large{ + left: 0; + text-align: left; + font-size: 13pt; + margin-top: -2px; + overflow-wrap: anywhere; + text-transform: capitalize; +} + +@media (min-width: 380px){ + .header-large{ + font-size: 13pt; + margin-top: 9px; + white-space: nowrap; + } +} +@media (min-width: 580px){ + .header-large{ + font-size: 19pt; + margin-top: 5px; + white-space: nowrap; + } +} + +.header-small { + text-align: right; + padding-right: 5px; + margin: 3px; +} + +/* NRCC Notices */ +#alerts{ + display: none; + position: fixed; + width: 100%; + left: 0; + top: 0; +} + +#alerts_bar{ + display: none; + position: absolute; + margin-top:50px; + left: 0; + width: 100%; + height: 40px; + background-color: var(--main-alert-color); + color: var(--second-text-color); + cursor: pointer; +} + +#alert_icon{ + position: absolute; + left: 10px; + margin-top: 5px; + width: 30px; + height: 30px; +} + +#alert_bar_note { + position: relative; + text-align: center; + margin: auto; + margin-top: 8px; + font-weight: 900; +} + +#alert_expand_arrow { + position: absolute; + right: 0; + top: 0; + padding: 10px; + padding-right: 15px; + padding-left: 15px; + background: none; + border: none; + font-weight: 900; + color: var(--second-text-color); + transition: transform 0.25s linear; + cursor: pointer; +} + +#alerts_msg{ + display: none; + position: absolute; + left: 0; + top: 40px; + width: 100%; + background-color: var(--main-alert-color); + background-image: radial-gradient(var(--second-alert-color) 10%,var(--main-alert-color) 70%); /* Undecided whether this actually looks better than plain orange? */ +} + +#alerts_msg p { + width: 90%; + margin-left: auto; + margin-right: auto; + font-weight: 900; +} + +/* Content */ +#output { + display: none; + width: 100%; + margin-top: 65px; +} + +table { + color: white; + width: 100%; + margin-top: 3px; + font-size: 10.5px; +} + +caption{ + padding-top: 5px; + padding-bottom: 10px; + font-size: larger; + font-weight: 900; +} + +.secondary-table{ + margin-top: 25px; +} +.name{ + width: 25%; + text-align: left; +} + +.detail-name-head { + text-align: left; +} + +.detail-name{ + text-align: left; +} +.name-item, .name, .plat, .time { + font-size: 12px; +} +.name-item { + color: var(--board-name-color); + cursor: pointer; +} + +.plat{ + width: 4%; + text-align: center; +} + +.time{ + width: 11.5%; + text-align: center; +} +.msg{ + width: 95%; + font-size: 10px; + margin: 0; + margin-left: 3px; + text-align: left; + color: var(--note-text-color); +} + +.close-data { + position: absolute; + right: 19px; + top: -8px; + font-weight: 900; + cursor: pointer; +} + +@media (min-width: 800px) { + .detail-name-head, .name-item, .name, .plat, .time, .close-data { + font-size: 16px; + } + .msg { + font-size: 13px + } +} +@media (min-width: 1000px) { + .detail-name-head, .name-item, .name, .plat, .time, .close-data { + font-size: 18px; + } + .msg { + font-size: 14px + } +} +@media (min-width: 1600px) { + .detail-name-head, .name-item, .name, .plat, .time, .close-data { + font-size: 20px; + } + .msg { + font-size: 15px + } +} + +.call-data { + display: none; + border-radius: 20px; + width: 93%; + max-height: 75%; + position: fixed; + z-index: 10; + top: 50px; + left: 0; + margin: 2%; + padding-top: 30px; + padding-left: 5px; + padding-right: 5px; + padding-bottom: 10px; + margin-bottom: 25px; + background-color: var(--overlay-color); + overflow: auto; +} + +.detail-name { + overflow: hidden; +} + +.detail-name:after { + content: ""; + display: inline-block; + height: 0.5em; + vertical-align: bottom; + width: 100%; + margin-right: -100%; + margin-left: 30px; + border-top: 1px solid; +} + +.call-table { + margin: auto; + width: 90%; +} + +.detail-name-here { + color: var(--board-name-color); +} + +.detail-table-content { + font-size: 13px; +} + +@media (min-width: 800px) { + .detail-table-content { + font-size: 20px; + } +} +@media (min-width: 1000px) { + .detail-table-content { + font-size: 21px; + } +} +@media (min-width: 1600px) { + .detail-table-content { + font-size: 22px; + } +} + +.changed{ + animation: pulse-change 1.5s linear infinite; +} +.cancelled { + animation: pulse-cancel 1.5s linear infinite; +} + +/* Footer: */ +#footer { + position: fixed; + bottom: -1px; + left: 0; + width: 100%; + height: 40px; + background-image: linear-gradient(to left, var(--accent-color), azure 190px); +} + +#footer img { + height: 25px; +} + +#nre_logo { + position: absolute; + left: 15px; + top: 6px; +} + +#owlboard_logo { + position: absolute; + right: 60px; + top: 8px; +} + +#home_icon { + position: absolute; + width: 10px; + right: 40px; + top: 8px; +} + +/* Animations */ +@keyframes pulse-change { + 50% { + color: var(--main-warning-color); + } +} + +@keyframes pulse-cancel { + 50% { + color: var(--main-alert-color); + } +} \ No newline at end of file diff --git a/styles/find-code.css b/styles/find-code.css new file mode 100644 index 0000000..6a35c47 --- /dev/null +++ b/styles/find-code.css @@ -0,0 +1,13 @@ +/*Overrides*/ +.titleimg{ + padding-bottom: 0px; +} +.small-lookup-box{ + width: 25%; + min-width: 75px; + max-width: 125px; +} +#name{ + width: 75%; + max-width: 275px; +} \ No newline at end of file diff --git a/styles/fonts/firamono/LICENSE b/styles/fonts/firamono/LICENSE new file mode 100644 index 0000000..1b5340a --- /dev/null +++ b/styles/fonts/firamono/LICENSE @@ -0,0 +1,95 @@ +** This license applies only to fonts within the same folder * + +Copyright (c) 2012-2013, The Mozilla Corporation and Telefonica S.A. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/styles/fonts/firamono/firamono-500.ttf b/styles/fonts/firamono/firamono-500.ttf new file mode 100644 index 0000000..793c60d Binary files /dev/null and b/styles/fonts/firamono/firamono-500.ttf differ diff --git a/styles/fonts/firamono/firamono-500.woff b/styles/fonts/firamono/firamono-500.woff new file mode 100644 index 0000000..79b051f Binary files /dev/null and b/styles/fonts/firamono/firamono-500.woff differ diff --git a/styles/fonts/firamono/firamono-500.woff2 b/styles/fonts/firamono/firamono-500.woff2 new file mode 100644 index 0000000..23e02eb Binary files /dev/null and b/styles/fonts/firamono/firamono-500.woff2 differ diff --git a/styles/fonts/firamono/firamono-regular.ttf b/styles/fonts/firamono/firamono-regular.ttf new file mode 100644 index 0000000..67bbd42 Binary files /dev/null and b/styles/fonts/firamono/firamono-regular.ttf differ diff --git a/styles/fonts/firamono/firamono-regular.woff b/styles/fonts/firamono/firamono-regular.woff new file mode 100644 index 0000000..a1884d7 Binary files /dev/null and b/styles/fonts/firamono/firamono-regular.woff differ diff --git a/styles/fonts/firamono/firamono-regular.woff2 b/styles/fonts/firamono/firamono-regular.woff2 new file mode 100644 index 0000000..3d31ce8 Binary files /dev/null and b/styles/fonts/firamono/firamono-regular.woff2 differ diff --git a/styles/fonts/urwgothic/LICENSING_DETAIL b/styles/fonts/urwgothic/LICENSING_DETAIL new file mode 100644 index 0000000..249f919 --- /dev/null +++ b/styles/fonts/urwgothic/LICENSING_DETAIL @@ -0,0 +1,55 @@ +https://www.tug.org/fonts/lppl-urw.txt + +https://lists.dante.de/pipermail/ctan-ann/2009-June/003741.html + +From: Jerzy B. Ludwichowski +Subject: URW++ making original 35 fonts available under LPPL + +I am forwarding the message below on behalf of Peter Rosenfeld, Managing +Director of URW++, who has kindly agreed to make the basic 35 PostScript +fonts also available under the LPPL. At his request, I'll be +disseminating the information throughout the TeX community. + +Many thanks to Dr. Rosenfeld! + +Many thanks are also due to Karl Berry, TUG President, for his unswerving +support and advice and Bogus\{}aw Jackowski, lead TeX Gyre developer, +for pushing the issue. + +Best, +Jerzy + +............................................................... + +From: Peter Rosenfeld +Date: Mon, 22 Jun 2009 11:33:29 +0200 +Subject: URW++ original 35 fonts available under LPPL + +To whom it may concern, + +Many years ago, URW++ Design and Development Inc. released their +Type 1 implementations of the basic 35 PostScript fonts under the +GNU General Public License and the Aladdin Ghostscript Free Public +License. + +We now additionally release them under the LaTeX Project Public License +(http://www.latex-project.org/lppl), either version 1 or (at your +option) any later version. + +Of course, this additional licensing applies to the original URW++ +material, not any subsequent changes and additions made by other +parties. + +The original font files are widely available, for instance as part of +the Ghostscript 4.00 release, and therefore we are not releasing any new +font files. Those interested can replace the license terms in those +original files accordingly. Responsibility for ensuring that no +material is incorrectly licensed remains with the distributor, as +always. + +We hope this additional licensing will make our fonts even more widely +available and usable in the free software community, such as the TeX +Gyre Project. + +Sincerely, +Peter Rosenfeld (Managing Director, URW++) diff --git a/styles/fonts/urwgothic/urwgothic.ttf b/styles/fonts/urwgothic/urwgothic.ttf new file mode 100644 index 0000000..2e5349a Binary files /dev/null and b/styles/fonts/urwgothic/urwgothic.ttf differ diff --git a/styles/fonts/urwgothic/urwgothic.woff b/styles/fonts/urwgothic/urwgothic.woff new file mode 100644 index 0000000..c7b2f8f Binary files /dev/null and b/styles/fonts/urwgothic/urwgothic.woff differ diff --git a/styles/fonts/urwgothic/urwgothic.woff2 b/styles/fonts/urwgothic/urwgothic.woff2 new file mode 100644 index 0000000..6d73cea Binary files /dev/null and b/styles/fonts/urwgothic/urwgothic.woff2 differ diff --git a/styles/fonts/urwgothic/urwgothicDemi.ttf b/styles/fonts/urwgothic/urwgothicDemi.ttf new file mode 100644 index 0000000..6838447 Binary files /dev/null and b/styles/fonts/urwgothic/urwgothicDemi.ttf differ diff --git a/styles/fonts/urwgothic/urwgothicDemi.woff b/styles/fonts/urwgothic/urwgothicDemi.woff new file mode 100644 index 0000000..81981aa Binary files /dev/null and b/styles/fonts/urwgothic/urwgothicDemi.woff differ diff --git a/styles/fonts/urwgothic/urwgothicDemi.woff2 b/styles/fonts/urwgothic/urwgothicDemi.woff2 new file mode 100644 index 0000000..515cdcb Binary files /dev/null and b/styles/fonts/urwgothic/urwgothicDemi.woff2 differ diff --git a/styles/help.css b/styles/help.css new file mode 100644 index 0000000..d53978f --- /dev/null +++ b/styles/help.css @@ -0,0 +1,13 @@ +/* Glossary */ +table, th, td { + border: 1px solid; + border-color: lightgrey; + border-collapse: collapse; + } + +#table { + color: lightgrey; + width: 80%; + max-width: 700px; + margin: auto; +} \ No newline at end of file diff --git a/styles/issue.css b/styles/issue.css new file mode 100644 index 0000000..b59d231 --- /dev/null +++ b/styles/issue.css @@ -0,0 +1,20 @@ +#preflight { + display: none; + border-radius: 20px; + width: 93%; + max-height: 80%; + position: fixed; + z-index: 10; + top: 50px; + left: 0; + margin: 2%; + padding-top: 30px; + padding-left: 5px; + padding-right: 5px; + padding-bottom: 10px; + margin-bottom: 25px; + background-color: var(--overlay-color); + color: var(--second-text-color); + overflow: auto; +} + diff --git a/styles/main.css b/styles/main.css new file mode 100644 index 0000000..f38a7a2 --- /dev/null +++ b/styles/main.css @@ -0,0 +1,318 @@ +/* FONTS */ +@font-face { + font-family: 'firamono'; + src: url('/styles/fonts/firamono/firamono-regular.woff2') format('woff2'), + url('/styles/fonts/firamono/firamono-regular.woff') format('woff'), + url('/styles/fonts/firamono/firamono-regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'firamono'; + src: url('/styles/fonts/firamono/firamono-500.woff2') format('woff2'), + url('/styles/fonts/firamono/firamono-500.woff') format('woff'), + url('/styles/fonts/firamono/firamono-500.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} +@font-face { + font-family: 'urwgothic'; + src: url('/styles/fonts/urwgothic/urwgothic.woff2') format('woff2'), + url('/styles/fonts/urwgothic/urwgothic.woff') format('woff'), + url('/styles/fonts/urwgothic/urwgothic.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} +@font-face { + font-family: 'urwgothic'; + src: url('/styles/fonts/urwgothic/urwgothicDemi.woff2') format('woff2'), + url('/styles/fonts/urwgothic/urwgothicDemi.woff') format('woff'), + url('/styles/fonts/urwgothic/urwgothicDemi.ttf') format('truetype'); + font-weight: 900; + font-style: normal; +} +/* COLOR VARS */ +:root { + --main-bg-color: #404c55; + --second-bg-color: #2b343c; /* Use as first arg in radial gradient */ + --accent-color: #007979; + --overlay-color: #3c6f79de; + --main-text-color: #00b7b7; + --second-text-color: azure; + --note-text-color: #9de7ff; + --link-color: azure; + --box-border-color: ; + --link-visited-color: azure; + --main-alert-color: #ed6d00; + --second-alert-color: #e77f00; /* Use as second arg in radial gradient */ + --main-warning-color: orange; + --board-name-color: #fcfc09; +} +/* Loading Box: */ +@keyframes spinner { + 0% { + transform: translate3d(-50%, -50%, 0) rotate(0deg); + } + 100% { + transform: translate3d(-50%, -50%, 0) rotate(360deg); + } +} +.spinner::before { + animation: 1.5s linear infinite spinner; + animation-play-state: inherit; + border: solid 5px var(--overlay-color); + border-bottom-color: var(--second-text-color); + border-radius: 50%; + content: ""; + height: 40px; + width: 40px; + position: absolute; + top: 30%; + margin: auto; + transform: translate3d(-50%, -50%, 0); + will-change: transform; +} +#loading { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + margin: auto; + background-color: var(--overlay-color); + border-radius: 45px; + padding: 20px; + padding-bottom: 1px; + min-width: 90px; + max-width: 90px; +} +#loading p { + padding-top: 50px; + font-weight: bolder; + overflow-wrap: normal; +} + +/* MAIN */ +html{ + width: 100%; + height: 100%; +} +body { + background-color: var(--main-bg-color); + background-image: radial-gradient(var(--second-bg-color), var(--main-bg-color)); + color: var(--main-text-color); + font-family: urwgothic, sans-serif; + text-align: center; + padding-bottom: 60px; /*Footer height*/ +} +body a {color:var(--link-color)} +body a:visited {color:var(--link-visited-color)} +.titleimg { + width: 45%; + padding-top: 20px; + padding-bottom: 20px; + width: 200px; + height: 131px; + transition: 0.2s; +} +@media only screen and (min-height: 740px) {.titleimg{width: 250px;height: 164px;}} + +.lookup-box { + text-align: center; + border: black; + border-radius: 40px; + padding: 10px; + margin-bottom: 5px; + font-size: 18px; + text-transform: uppercase; + font-family: urwgothic, sans-serif; + transition: 0.2s; +} +.text-entry { + text-align: center; + border: black; + width: 75%; + max-width: 250px; + border-radius: 40px; + padding: 10px; + margin-bottom: 5px; + font-size: 12px; + font-family: urwgothic, sans-serif; + transition: 0.2s; +} +.text-entry-long{ + text-align: left; + border: black; + width: 75%; + max-width: 250px; + height: 30%; + max-height: 350px; + border-radius: 20px; + padding: 10px; + margin-bottom: 5px; + font-size: 12px; + font-family: urwgothic, sans-serif; + transition: 0.2s; +} +label { + font-weight: 900; +} +.small-lookup-box { + text-align: center; + border: black; + border-radius: 40px; + padding: 10px; + margin-bottom: 10px; + text-transform: uppercase; + font-family: urwgothic, sans-serif; + transition: 0.2s; +} +.form-text-small { + text-align: center; + border: black; + width: 80%; + border-radius: 5px; + padding: 10px; + font-size: 18px; + transition: 0.2s; +} +@media only screen and (min-width: 600px) {.form-text-small{width: 50%}} +.form-text-large { + text-align: left; + border: black; + width: 80%; + height: 90px; + border-radius: 5px; + padding: 5px; + font-size: 16px; + transition: 0.2s; +} +@media only screen and (min-width: 600px) {.form-text-large{width: 50%}} +.form-info { + color: var(--main-text-color); + font-size: 17px; + font-weight: bolder; + margin-bottom: 4px; +} +.text-description { + display: inline-block; + width: 80%; + font-family: sans-serif; + color: var(--main-text-color); + padding-top: 5px; + padding-bottom: 5px; + margin-left: auto; + margin-right: auto; + transition: 0.2s; +} +@media only screen and (min-width: 600px) {.text-description{width: 50%}} +.lookup-button { + background-color: var(--accent-color); + color: var(--link-color); + border: none; + border-radius: 18px; + font-size: 16px; + font-weight: normal; + font-family: urwgothic, sans-serif; + padding: 5px; + padding-left: 15px; + padding-right: 15px; + margin-bottom: 10px; + cursor: pointer; +} +#quick_links{ + width: 75%; + max-width: 300px; + margin: auto; +} +.actionbutton { + display: inline-block; + text-decoration: none; + font-family: firamono, monospace; + font-weight: 400; + cursor: pointer; + background-color: var(--accent-color); + border: none; + border-radius: 10px; + color: var(--link-color); + padding: 3px; + padding-left: 8px; + padding-right: 8px; + margin-left: 5px; + margin-right: 5px; + margin-bottom: 10px; + font-size: 18px; +} +.inlinelink { + text-decoration: underline; + color: var(--link-color); + cursor: pointer; +} +/* START MENU STYLE */ +#top_button { + position: absolute; + top: 2px; + right: 5px; + padding: 5px; +} +.sidebar_control { + background-color: transparent; + color: var(--link-color); + border: none; + font-family: sans-serif; + font-size: larger; + cursor: pointer; +} +#sidebar_open_short {display: block;} +#sidebar_close_short { + display: none; + font-size: x-large; +} +#sidebar { + position: fixed; + top: 40px; + right: 0; + margin: auto; + display: block; + max-width: 250px; + width: 0; + border-top-left-radius: 45px; + border-bottom-left-radius: 45px; + background-color: var(--overlay-color); + transition: 0.4s; +} +#sidebar a { + padding: 8px 8px 8px 8px; + margin-top: 10px; + margin-bottom: 10px; + font-family: urwgothic, sans-serif; + font-weight: 300; + text-decoration: none; + text-align: center; + font-size: 25px; + color: var(--link-color); + display: block; + white-space: nowrap; + transition: 0.5s; +} + +/* Footer Styles */ +footer { + background-color: var(--accent-color); + font-family: firamono, monospace; + font-size: smaller; + color: var(--second-text-color); + width: 100%; + position: fixed; + bottom: -1px; + left: 0; +} +footer a { + text-decoration: underline; + color: var(--link-color); +} +footer a:visited { + color: var(--link-visited-color); +} +footer a:hover { + color: beige; +} \ No newline at end of file diff --git a/styles/settings.css b/styles/settings.css new file mode 100644 index 0000000..0ba70b7 --- /dev/null +++ b/styles/settings.css @@ -0,0 +1,31 @@ +.small-lookup-box{ + max-width: 100px; + height: 20px; +} + +#done { + opacity: 0; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + margin: auto; + background-color: var(--overlay-color); + border-radius: 45px; + padding: 20px; + padding-bottom: 1px; + min-width: 90px; + max-width: 90px; + transition: opacity 0.25s; + } + +#done img { + width: 80px; + height: 80px; +} + +#done p { + padding-top: 0px; + font-weight: bolder; + overflow-wrap: normal; + } \ No newline at end of file diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..fb47816 --- /dev/null +++ b/sw.js @@ -0,0 +1,82 @@ +/* Service Worker */ + +const appVersion = "0.0.3" +const cacheName = `owlboard-${appVersion}` +const cacheIDs = [cacheName] +const cacheFiles = [ + "/404.html", + "/conn-err.html", + "/help.html", + "/", + "/issue.html", + "/find-code.html", + "/settings.html", + "/manifest.json", + "/styles/fonts/firamono/firamono-500.woff2", + "/styles/fonts/firamono/firamono-regular.woff2", + "/styles/fonts/urwgothic/urwgothic.woff2", + "/styles/fonts/urwgothic/urwgothicDemi.woff2", + "/styles/boards.css", + "/styles/find-code.css", + "/styles/help.css", + "/styles/issue.css", + "/styles/main.css", + "/styles/settings.css", + "/js/find-code.js", + "/js/index.js", + "/js/issue.js", + "/js/lib.board.js", + "/js/lib.main.js", + "/js/settings.js", + "/js/simple-board.js", + "/images/icon.svg", + "/images/logo/wide_logo.svg", + "/images/logo/mono-logo.svg", + "images/app-icons/any/plain-logo.svg", + "images/app-icons/any/plain-logo-512.png", + "/images/nav/alert_icon.svg", + "/images/nav/save.svg", + "/images/nav/home_icon.svg", + "/images/nav/back.svg", + "/images/nav/hamburger.svg", + "/images/nav/close.svg", + "/images/nre/nre-powered_400w.webp", + "/images/nre/nre-powered_400w.jxl" +] + +self.addEventListener("install", (e) => { + console.log("[Service Worker] Install"); + e.waitUntil( + (async () => { + const cache = await caches.open(cacheName); + console.log("[Service Worker] Caching app data"); + await cache.addAll(cacheFiles); + })() + ); + }); + + self.addEventListener("fetch", (e) => { + e.respondWith( + (async () => { + const r = await caches.match(e.request); + if (r) { + return r; + } + const response = await fetch(e.request); + console.log(`[Service Worker] Not cached - fetching from server: ${e.request.url}`); + return response; + })() + ); + }); + + self.addEventListener('activate', function (event) { + event.waitUntil(caches.keys().then(function (keys) { + return Promise.all(keys.filter(function (key) { + return !cacheIDs.includes(key); + }).map(function (key) { + return caches.delete(key); + })); + }).then(function () { + return self.clients.claim(); + })); + }); \ No newline at end of file