Adjust display logic

This commit is contained in:
2026-05-23 01:18:39 +01:00
parent 6a99ffc806
commit 0a9fb02b9f
3 changed files with 109 additions and 91 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { ApiStationsBoard } from '@owlboard/owlboard-ts'; import type { ApiStationsBoard } from '@owlboard/owlboard-ts';
import { formatUkTime, estClass, delayClassFromTimePair } from '$lib/utils/time'; import { formatUkTime, estClass, delayClassFromTimePair, isRightTime } from '$lib/utils/time';
import { fade, slide, fly } from 'svelte/transition'; import { fade, slide, fly } from 'svelte/transition';
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
@@ -11,7 +11,7 @@
</script> </script>
<section class="departure-board"> <section class="departure-board">
<div class="header"> <div class="header container">
<div class="header row"> <div class="header row">
<div class="upper-header-blank"></div> <div class="upper-header-blank"></div>
<div class="upper-header-text">Arr</div> <div class="upper-header-text">Arr</div>
@@ -36,9 +36,14 @@
<div <div
class="service-block" class="service-block"
animate:flip={{ duration: 400 }} animate:flip={{ duration: 400 }}
transition:slide={{duration:300}} transition:slide={{ duration: 300 }}
>
<div
class="service row"
class:passing={service.wtp}
class:nonPax={!service.ip}
class:cancelled={service.c}
> >
<div class="service row">
<div class="cell id">{service.h}</div> <div class="cell id">{service.h}</div>
<div class="cell orig"> <div class="cell orig">
{#key service.og.t} {#key service.og.t}
@@ -50,7 +55,7 @@
<div class="cell dest"> <div class="cell dest">
{#key service.dt.t}<div transition:fade={{ duration: 300 }}>{service.dt.t}</div>{/key} {#key service.dt.t}<div transition:fade={{ duration: 300 }}>{service.dt.t}</div>{/key}
</div> </div>
<div class="cell plt"> <div class="cell plt" class:platsup={service.ps} class:platchange={service.pc}>
{#key service.p}<div transition:fade={{ duration: 300 }}>{service.p || '-'}</div>{/key} {#key service.p}<div transition:fade={{ duration: 300 }}>{service.p || '-'}</div>{/key}
</div> </div>
{#if service.wtp} {#if service.wtp}
@@ -58,35 +63,47 @@
<div class="cell sch"> <div class="cell sch">
{formatUkTime(service.wtp)} {formatUkTime(service.wtp)}
</div> </div>
<div class="cell act"> <div
{#key realPass} class="cell act {estClass(service.atp, service.etp)} {delayClassFromTimePair(service.wtp, service.atp || service.etp)}"
<div transition:fade={{ duration: 300 }}> >
{#if isRightTime(service.wtp, service.atp || service.etp)}
RT
{:else if service.c}
CANC
{:else}
{realPass} {realPass}
</div> {/if}
{/key}
</div> </div>
{:else} {:else}
<div class="cell sch">{formatUkTime(service.sta)}</div> <div class="cell sch">{formatUkTime(service.sta)}</div>
<div class="cell act"> <div
{#key realArrival} class="cell act {estClass(service.ata, service.eta)} {delayClassFromTimePair(service.sta, service.ata || service.eta)}"
<div transition:fade={{ duration: 300 }}> >
{#if isRightTime(service.sta, service.ata || service.eta)}
RT
{:else if service.sta && service.c} CAN {:else}
{realArrival} {realArrival}
</div> {/if}
{/key}
</div> </div>
<div class="cell sch">{formatUkTime(service.std)}</div> <div class="cell sch">{formatUkTime(service.std)}</div>
<div class="cell act"> <div class="cell act {estClass(service.atd, service.etd)} {delayClassFromTimePair(service.std, service.atd || service.etd)}">
{#key realDeparture} {#if isRightTime(service.std, service.atd || service.etd)}
<div transition:fade={{ duration: 300 }}> RT
{:else if service.std && service.c} CANC {:else}
{realDeparture} {realDeparture}
</div> {/if}
{/key}
</div> </div>
{/if} {/if}
</div> </div>
<div class="operator row">{service.o}</div> <div class="operator row">{service.o}</div>
{#if service?.cr?.r} {#if service?.cr?.r}
{#if service.c}
<div class="cancel-reason row" transition:slide={{ duration: 300 }}>{service.cr.r}</div> <div class="cancel-reason row" transition:slide={{ duration: 300 }}>{service.cr.r}</div>
{:else}
<div class="cancel-reason row" transition:slide={{ duration: 300 }}>
Part of this service has been cancelled
</div>
{/if}
{:else if service?.dr?.r} {:else if service?.dr?.r}
<div class="delay-reason row" transition:slide={{ duration: 300 }}>{service.dr.r}</div> <div class="delay-reason row" transition:slide={{ duration: 300 }}>{service.dr.r}</div>
{/if} {/if}
@@ -104,6 +121,13 @@
overflow: hidden; overflow: hidden;
} }
.header.container {
position: sticky;
top: 0;
z-index: 2;
background: var(--color-bg-dark);
}
.row.header { .row.header {
font-weight: 875; font-weight: 875;
} }
@@ -131,6 +155,17 @@
font-variant-ligatures: additional-ligatures; font-variant-ligatures: additional-ligatures;
} }
.service.row.passing {
font-style: italic;
opacity: 0.25;
font-weight: 200;
}
.service.row.nonPax {
opacity: 0.25;
font-weight: 200;
}
.service-block { .service-block {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
@@ -142,52 +177,11 @@
width: 100%; width: 100%;
} }
.departure-board td {
font-family: 'Inconsolata Variable', monospace;
font-size: clamp(1rem, 0.475rem + 2.8vw, 1.35rem);
font-variant-ligatures: additional-ligatures;
overflow: hidden;
}
thead,
.cancel-row td,
.delay-row td {
letter-spacing: -0.15ch;
}
thead {
position: sticky;
top: 0;
z-index: 2;
background: var(--color-bg-dark);
}
abbr {
text-decoration: none;
border-bottom: none;
cursor: help;
}
tbody tr:first-child td {
border-top: 5px solid transparent;
}
/* Row Logic */ /* Row Logic */
.serviceCancelled { .service.cancelled {
color: rgb(255, 131, 131); color: rgb(255, 131, 131);
} }
.servicePass td {
opacity: 0.75;
font-weight: 200;
}
.serviceNonPassenger td {
opacity: 0.5;
font-weight: 290;
font-style: italic;
}
/* Special Row Styles */ /* Special Row Styles */
.operator.row, .operator.row,
.delay-reason.row, .delay-reason.row,
@@ -200,11 +194,13 @@
.cancel-reason.row { .cancel-reason.row {
color: rgb(255, 131, 131); color: rgb(255, 131, 131);
font-weight: 400; font-weight: 400;
letter-spacing: -0.1ch;
} }
.delay-reason.row { .delay-reason.row {
color: var(--delay-orange); color: var(--delay-orange);
font-style: italic; font-weight: 400;
letter-spacing: -0.1ch;
} }
@media (min-width: 375px) { @media (min-width: 375px) {
.operator.row, .operator.row,
@@ -314,16 +310,12 @@
font-stretch: 110%; font-stretch: 110%;
} }
} }
.cell.plt.platSup { .cell.plt.platsup {
font-weight: 200; font-weight: 150;
opacity: 0.3;
} }
.cell.plt.platChange { .cell.plt.platchange {
animation: fast-pulse 2s ease-out infinite; animation: fast-pulse 2s ease-out infinite;
} }
.service-row.serviceCancelled .plt-cell {
text-decoration: line-through;
}
.pass-cell { .pass-cell {
text-align: center; text-align: center;
font-stretch: 100%; font-stretch: 100%;
@@ -343,6 +335,12 @@
text-align: center; text-align: center;
font-stretch: 72%; font-stretch: 72%;
} }
.cell.act.delay-late {
color: var(--delay-orange);
}
.cell.act.delay-early {
color: var(--early-blue);
}
@media (min-width: 350px) { @media (min-width: 350px) {
.cell.sch, .cell.sch,
.cell.act { .cell.act {
@@ -367,21 +365,10 @@
font-stretch: 100%; font-stretch: 100%;
} }
} }
.cell.act.est {
/* RT Logic */ font-style: italic;
.time-cell.delay-rt span { font-weight: 300;
display: none; opacity: 0.5;
}
.time-cell.delay-rt::after {
content: 'RT';
}
.time-cell.delay-early {
color: var(--early-blue);
}
.time-cell.delay-late {
color: var(--delay-orange);
} }
/* Time Types */ /* Time Types */
@@ -390,7 +377,7 @@
opacity: 0.75; opacity: 0.75;
font-weight: 350; font-weight: 350;
} }
.cell.act { .cell.act.actual {
font-weight: 600; font-weight: 600;
} }
</style> </style>

View File

@@ -1,6 +1,6 @@
import type { ApiTrainsTrainDetails } from '@owlboard/owlboard-ts'; import type { ApiTrainsTrainDetails } from '@owlboard/owlboard-ts';
export const estClass = (act: any, est: any) => (act ? 'act' : 'est'); export const estClass = (act: any, est: any) => (act ? 'actual' : 'estimate');
/** /**
* Converts ISO/JSON time to UK-formatted HH:MM string, with optional (default off) seconds * Converts ISO/JSON time to UK-formatted HH:MM string, with optional (default off) seconds
@@ -132,3 +132,23 @@ export function delayClassFromTimePair(sched: any, act: any): string {
return diff > 0 ? 'delay-late' : 'delay-early'; return diff > 0 ? 'delay-late' : 'delay-early';
} }
/**
* Accepts a pair of times (string or Date) and returns true if service considered 'on-time'
* @param sched Scheduled Time (string, Date)
* @param act Actual Time (string, Date)
*/
export function isRightTime(
sched: string | Date | undefined,
act: string | Date | undefined
): boolean {
console.log(`Checking [sched: ${sched}, act: ${act}]`);
if (!sched || !act) return false;
const s = new Date(sched).getTime();
const a = new Date(act).getTime();
if (isNaN(s) || isNaN(a)) return false;
return Math.abs(a - s) < 60000;
}

View File

@@ -130,6 +130,10 @@
<div class="service-list-wrapper"> <div class="service-list-wrapper">
<StaffServicesGrid services={data.boardData.data.s} /> <StaffServicesGrid services={data.boardData.data.s} />
</div> </div>
{:else}
<div class="no-service">
No services at this location in the next three hours
</div>
{/if} {/if}
</section> </section>
@@ -145,6 +149,13 @@
gap: 0; gap: 0;
} }
.no-service {
display: block;
margin: auto;
max-width: 75%;
font-family: 'URW Gothic', sans-serif;
}
.time-data { .time-data {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;