Compare commits

..

7 Commits

8 changed files with 133 additions and 50 deletions

View File

@ -64,7 +64,7 @@
top: 5px; top: 5px;
right: 0; right: 0;
display: flex; display: flex;
gap: 0px gap: 0px;
} }
.content { .content {

View File

@ -15,8 +15,8 @@
<Card config={upstreamConfig}> <Card config={upstreamConfig}>
<form action={config.formAction}> <form action={config.formAction}>
<input type="text" name={config.fieldName} placeholder={config.placeholder} maxlength={config.maxLen} autocomplete="off"> <input type="text" name={config.fieldName} placeholder={config.placeholder} maxlength={config.maxLen} autocomplete="off" />
<br> <br />
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
</Card> </Card>
@ -50,7 +50,6 @@
input:hover { input:hover {
box-shadow: var(--box-shadow-dark); box-shadow: var(--box-shadow-dark);
} }
button:hover { button:hover {

View File

@ -6,50 +6,80 @@
import type { NearestStationResponse } from "@owlboard/ts-types"; import type { NearestStationResponse } from "@owlboard/ts-types";
import { uuid } from "$lib/stores/uuid"; import { uuid } from "$lib/stores/uuid";
import { location } from "$lib/stores/location"; import { location } from "$lib/stores/location";
import InLineLoading from "$lib/navigation/InLineLoading.svelte";
import { apiGet } from "$lib/scripts/apiFetch";
import { onMount } from "svelte";
let stations: NearestStationResponse[]; let errorMessage: string;
let stations: NearestStationResponse[] = [];
let config: CardConfig = { let config: CardConfig = {
title: "Near to Me", title: "Near to Me",
showHelp: true, showHelp: true,
showRefresh: true, showRefresh: true,
helpText: "Your location may not be accurate, particularly on desktop and laptop devices.", helpText: "Your location may not be accurate on desktop and laptop devices.",
onRefresh: refresh onRefresh: refresh,
} };
function turnOnLocation() { function turnOnLocation() {
location.set(true) location.set(true);
getCurrentLocation(); getCurrentLocation();
toast.success("Done\nTo disable location, go to settings") toast.success("Done\nTo disable location, go to settings");
} }
function refresh() {getNearestStations()} function refresh() {
stations = [];
getNearestStations();
}
async function getNearestStations() { async function getNearestStations() {
// Get location, then fetch nearest stations and push to `stations` variable // Get location, then fetch nearest stations and push to `stations` variable
const currentLocation = await getCurrentLocation();
const apiPath: string = `/api/v2/live/station/nearest/${currentLocation.latitude}/${currentLocation.longitude}`;
try {
const apiResponse = (await apiGet(apiPath)) as NearestStationResponse[];
stations = apiResponse;
} catch (err) {
errorMessage = err as string;
}
} }
onMount(() => {
if ($location) {
if ($uuid && $uuid != "null") {
getNearestStations();
} else {
errorMessage = "Register to use this feature";
}
}
});
</script> </script>
<Card {config}> <Card {config}>
{#if !$uuid || $uuid === "null"} {#if !$uuid || $uuid === "null"}
<p><a href="/more/reg">Register to use this feature</a></p> <p><a href="/more/reg">Register to use this feature</a></p>
{:else} {:else if $location}
{#if $location} {#if !stations.length}
{#if !stations} {#if errorMessage}
<p>Fetching locations...</p> <p>{errorMessage}</p>
{:else} {:else}
{#each stations as station} <InLineLoading />
<a href="/ldb?station={station["3ALPHA"]}">{station.NLCDESC} - {station.miles}mi</a>
{/each}
{/if} {/if}
{:else} {:else}
<p><button on:click={turnOnLocation}>Turn on Location</button></p> <div id="buttons">
{#each stations as station}
<a class="link" href="/ldb?station={station['3ALPHA']}">{station.NLCDESC} - {station.miles}mi</a>
{/each}
</div>
{/if} {/if}
{:else}
<p><button on:click={turnOnLocation}>Turn on Location</button></p>
{/if} {/if}
</Card> </Card>
<style> <style>
a, button { a,
button {
color: aliceblue; color: aliceblue;
text-decoration: none; text-decoration: none;
margin: auto; margin: auto;
@ -61,8 +91,30 @@
transition: all 0.3s ease; transition: all 0.3s ease;
} }
a:hover, button:hover { a:hover,
button:hover {
background-color: rgb(45, 45, 45); background-color: rgb(45, 45, 45);
box-shadow: var(--box-shadow-dark); box-shadow: var(--box-shadow-dark);
} }
#buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 95%;
margin: auto;
padding-top: 5px;
}
.link {
display: inline-flex;
margin: 5px;
border: none;
border-radius: 20px;
padding: 5px 10px;
font-family: urwgothic, "Lucida Sans", "Lucida Sans Regular", "Lucida Grande", "Lucida Sans Unicode", Geneva, Verdana, sans-serif;
font-size: 16px;
font-weight: 400;
white-space: nowrap;
}
</style> </style>

View File

@ -0,0 +1,28 @@
<script lang="ts">
export let size: string = "1em";
export let color: string = "aliceblue";
</script>
<div class="spinner" style="--spinner-size: {size}; --spinner-color: {color};" />
<style>
.spinner {
display: inline-block;
width: var(--spinner-size);
height: var(--spinner-size);
border: 2px solid transparent;
border-top-color: var(--spinner-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 10px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -1,32 +1,32 @@
<script> <script lang="ts">
import { getApiUrl } from "$lib/scripts/upstream"; import { apiGet } from "$lib/scripts/apiFetch";
import { uuid } from "$lib/stores/uuid"; import { ReasonCode } from "@owlboard/ts-types";
export let code = ""; export let code: string;
export let type = ""; export let type: string;
async function getDelay(code = "") { async function getDelay(code: string): Promise<string | undefined> {
console.log(`Fetching delay reason ${code}`);
const data = await getReason(code); const data = await getReason(code);
return data[0].lateReason || "This train has been delayed"; if (data) {
return data[0].lateReason || "This train has been delayed";
}
} }
async function getCancel(code = "") { async function getCancel(code: string): Promise<string | undefined> {
console.log(`Fetching cancel reason ${code}`);
const data = await getReason(code); const data = await getReason(code);
return data[0].cancReason || "This train has been cancelled"; if (data) {
return data[0].cancReason || "This train has been cancelled";
}
} }
async function getReason(code = "") { async function getReason(code: string): Promise<ReasonCode[] | undefined> {
const url = `${getApiUrl()}/api/v2/ref/reasonCode/${code}`; const apiString = `/api/v2/ref/reasonCode/${code}`;
const options = { try {
method: "GET", const apiRes = (await apiGet(apiString)) as ReasonCode[];
headers: { return apiRes;
uuid: $uuid, } catch (err) {
}, console.error("Unable to define reason code");
}; }
const res = await fetch(url, options);
return await res.json();
} }
</script> </script>

View File

@ -34,8 +34,12 @@ export async function apiGet(path: string): Promise<any> {
try { try {
const res = await fetch(getUrlString() + path, options); const res = await fetch(getUrlString() + path, options);
if (res.status === 401) {
throw new Error("Registration not accepted. Register at `Menu > Registration`");
}
if (!res.ok) { if (!res.ok) {
throw new Error("Network response not ok"); throw new Error(`Failed: ${res.status}: ${res.statusText}`);
} }
const contentType = res.headers.get("content-type"); const contentType = res.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) { if (!contentType || !contentType.includes("application/json")) {

View File

@ -1,3 +1,3 @@
export const version: string = "2024.07.1"; export const version: string = "2024.07.2";
export const versionTag: string = ""; export const versionTag: string = "";
export const showWelcome: boolean = false; export const showWelcome: boolean = false;

View File

@ -18,7 +18,7 @@
placeholder: "Enter CRS/TIPLOC/STANOX", placeholder: "Enter CRS/TIPLOC/STANOX",
maxLen: 7, maxLen: 7,
formAction: "/ldb/", formAction: "/ldb/",
fieldName: "station" fieldName: "station",
}; };
let TimetableConfig: LookupCardConfig = { let TimetableConfig: LookupCardConfig = {
@ -27,8 +27,8 @@
placeholder: "Enter headcode", placeholder: "Enter headcode",
maxLen: 4, maxLen: 4,
formAction: "/train/", formAction: "/train/",
fieldName: "headcode" fieldName: "headcode",
} };
function onRefresh() { function onRefresh() {
console.log("Refresh"); console.log("Refresh");