287 lines
8.5 KiB
Svelte
287 lines
8.5 KiB
Svelte
<script>
|
|
import OverlayIsland from "$lib/islands/overlay-island.svelte";
|
|
import { fade } from "svelte/transition";
|
|
import Reason from "$lib/raw-fetchers/reason.svelte";
|
|
import { uuid } from "$lib/stores/uuid";
|
|
import StylesToc from "$lib/train/styles-toc.svelte";
|
|
import { getApiUrl } from "$lib/scripts/upstream";
|
|
export let detail = {
|
|
uid: "",
|
|
rid: "",
|
|
headcode: "",
|
|
show: true,
|
|
};
|
|
export let close;
|
|
|
|
function handleClick() {
|
|
close();
|
|
}
|
|
|
|
async function getTrain(rid) {
|
|
try {
|
|
console.log(`Requested Station: ${rid}`);
|
|
const url = `${getApiUrl()}/api/v2/live/train/rid/${rid}`;
|
|
const opt = {
|
|
method: "GET",
|
|
headers: {
|
|
uuid: $uuid,
|
|
},
|
|
};
|
|
const data = await fetch(url, opt);
|
|
return await data.json();
|
|
} catch (error) {
|
|
console.error("Error fetching data:", error);
|
|
}
|
|
}
|
|
|
|
async function parseDelay(location) {
|
|
let string, state;
|
|
if (location?.lateness) {
|
|
try {
|
|
const result = Math.floor(location.lateness / 60);
|
|
if (result === 0) {
|
|
(string = "RT"), (state = "");
|
|
} else if (result < 0) {
|
|
(string = -result + "E"), (state = "early");
|
|
} else if (result > 0) {
|
|
(string = result + "L"), (state = "late");
|
|
}
|
|
} catch {
|
|
(string = ""), (state = "");
|
|
}
|
|
} else if (location.arrivalType === "Delayed") {
|
|
(string = ""), (state = "late");
|
|
} else {
|
|
(string = ""), (state = "noreport");
|
|
}
|
|
return {
|
|
string: string,
|
|
state: state,
|
|
};
|
|
}
|
|
|
|
function parseTime(date) {
|
|
const parsedTime = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
return parsedTime !== "Invalid Date" ? parsedTime : null;
|
|
}
|
|
|
|
function parseTimes(service) {
|
|
const sta = new Date(service.sta),
|
|
eta = new Date(service.eta),
|
|
ata = new Date(service.ata);
|
|
const std = new Date(service.std),
|
|
etd = new Date(service.etd),
|
|
atd = new Date(service.atd);
|
|
let parsedSta = parseTime(sta),
|
|
parsedEta = parseTime(eta),
|
|
parsedAta = parseTime(ata);
|
|
let parsedStd = parseTime(std),
|
|
parsedEtd = parseTime(etd),
|
|
parsedAtd = parseTime(atd);
|
|
if (service.isCancelled) {
|
|
(parsedEta = "CANC"), (parsedEtd = "CANC");
|
|
}
|
|
let times = {
|
|
sta: parsedSta || "-",
|
|
eata: parsedEta || parsedAta || "-",
|
|
aEst: parsedEta ? "estimate" : "",
|
|
std: parsedStd || "-",
|
|
eatd: parsedEtd || parsedAtd || "-",
|
|
dEst: parsedEtd ? "estimate" : "",
|
|
};
|
|
if (service.isCancelled) {
|
|
(parsedEta = "CANC"), (parsedEtd = "CANC");
|
|
(times.aEst = "canc"), (times.dEst = "canc");
|
|
}
|
|
return times;
|
|
}
|
|
</script>
|
|
|
|
<OverlayIsland>
|
|
<div id="detailBox">
|
|
<button type="button" id="closeService" on:click={handleClick}>X</button>
|
|
{#await getTrain(detail.rid)}
|
|
<h6>{detail.headcode}</h6>
|
|
<p in:fade id="loading">Loading Data...</p>
|
|
{:then train}
|
|
<h6><StylesToc toc={train.GetServiceDetailsResult.operatorCode} full={true} /> {detail.headcode}</h6>
|
|
<p>
|
|
Locations in grey are not scheduled stops
|
|
<br />
|
|
Times in <span class="estimate">yellow</span> are estimated times
|
|
</p>
|
|
{#if train.GetServiceDetailsResult.delayReason}
|
|
<p class="reason late">
|
|
<Reason type="delay" code={train.GetServiceDetailsResult.delayReason} />
|
|
</p>
|
|
{/if}
|
|
{#if train.GetServiceDetailsResult.cancelReason}
|
|
<p class="reason canc">
|
|
<Reason type="cancel" code={train.GetServiceDetailsResult.cancelReason} />
|
|
</p>
|
|
{/if}
|
|
<table id="detailTable">
|
|
<tr>
|
|
<th class="tableLocation">Loc.</th>
|
|
<th class="tablePlatform">Pl.</th>
|
|
<th class="tableTime">Sch</th>
|
|
<th class="tableTime">Est/<br />Act</th>
|
|
<th class="tableTime">Sch</th>
|
|
<th class="tableTime">Est/<br />Act</th>
|
|
<th class="tableDelay" />
|
|
</tr>
|
|
<tr>
|
|
<th colspan="2" />
|
|
<th colspan="2">Arrival</th>
|
|
<th colspan="2">Departure</th>
|
|
<th />
|
|
</tr>
|
|
{#each train.GetServiceDetailsResult.locations.location as location}
|
|
<tr>
|
|
<td class="location {location?.isPass === 'true' ? 'pass' : ''}">{location.tiploc}</td>
|
|
<td class={location?.isPass === "true" ? "pass" : ""}>{location.platform || ""}</td>
|
|
{#await parseTimes(location)}
|
|
<td />
|
|
<td />
|
|
<td />
|
|
<td />
|
|
{:then times}
|
|
<td class={location?.isPass === "true" ? "pass" : ""}>{times.sta}</td>
|
|
<td class="{location?.isPass === 'true' ? 'pass' : ''} {times.aEst}">{times.eata}</td>
|
|
<td class={location?.isPass === "true" ? "pass" : ""}>{times.std}</td>
|
|
<td class="{location?.isPass === 'true' ? 'pass' : ''} {times.dEst}">{times.eatd}</td>
|
|
{/await}
|
|
{#await parseDelay(location)}
|
|
<td>-</td>
|
|
{:then delay}
|
|
<td class={delay.state}>{delay.string}</td>
|
|
{/await}
|
|
</tr>
|
|
{/each}
|
|
</table>
|
|
{:catch}
|
|
<h6>Error loading data</h6>
|
|
{/await}
|
|
</div>
|
|
</OverlayIsland>
|
|
|
|
<style>
|
|
#detailBox {
|
|
width: 100%;
|
|
min-height: 100px;
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
}
|
|
h6 {
|
|
position: absolute;
|
|
top: -16px;
|
|
left: 20px;
|
|
font-size: 14px;
|
|
color: whitesmoke;
|
|
}
|
|
#loading {
|
|
color: white;
|
|
animation: pulse-early 2.5s linear infinite;
|
|
}
|
|
p {
|
|
margin-top: 45px;
|
|
margin-bottom: 0px;
|
|
}
|
|
p.reason {
|
|
margin-top: 5px;
|
|
}
|
|
#closeService {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 10px;
|
|
border: none;
|
|
border-radius: 60px;
|
|
width: 35px;
|
|
height: 35px;
|
|
background-color: var(--main-bg-color);
|
|
color: white;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
}
|
|
#detailTable {
|
|
margin-top: 10px;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
margin-top: 12px;
|
|
margin-left: 0px;
|
|
margin-right: 0px;
|
|
padding: 0px;
|
|
color: white;
|
|
font-family: ubuntu, monospace;
|
|
font-size: 16px;
|
|
}
|
|
@media screen and (max-width: 338px) {
|
|
#detailTable {
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
@media screen and (max-width: 301px) {
|
|
#detailTable {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
@media screen and (min-width: 469px) {
|
|
#detailTable {
|
|
font-size: 20px;
|
|
}
|
|
}
|
|
.tableLocation {
|
|
width: 18%;
|
|
}
|
|
td.location {
|
|
color: yellow;
|
|
}
|
|
.tablePlatform {
|
|
width: 9%;
|
|
}
|
|
.tableTime {
|
|
width: 14%;
|
|
}
|
|
.tableDelay {
|
|
width: 7%;
|
|
}
|
|
.estimate {
|
|
color: rgb(255, 255, 119);
|
|
}
|
|
.pass {
|
|
color: white !important;
|
|
opacity: 0.45;
|
|
}
|
|
.canc {
|
|
color: white;
|
|
animation: pulse-cancel 1.5s linear infinite;
|
|
}
|
|
|
|
.early {
|
|
animation: pulse-early 1.5s linear infinite;
|
|
}
|
|
|
|
.late {
|
|
color: white;
|
|
animation: pulse-late 1.5s linear infinite;
|
|
}
|
|
|
|
@keyframes pulse-late {
|
|
50% {
|
|
color: var(--main-warning-color);
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-cancel {
|
|
50% {
|
|
color: var(--main-alert-color);
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-early {
|
|
50% {
|
|
color: rgb(136, 164, 255);
|
|
}
|
|
}
|
|
</style>
|