387 lines
8.1 KiB
Svelte
387 lines
8.1 KiB
Svelte
<script lang="ts">
|
|
import type { ApiStationsBoard } from '@owlboard/owlboard-ts';
|
|
import { formatUkTime, estClass, delayClassFromTimePair, isRightTime } from '$lib/utils/time';
|
|
import { fade, slide, fly } from 'svelte/transition';
|
|
import { flip } from 'svelte/animate';
|
|
|
|
let { services }: { services: ApiStationsBoard.BoardService[] } = $props();
|
|
|
|
const getRowKey = (s: ApiStationsBoard.BoardService) =>
|
|
`${s.r}${s.sta ?? ''}${s.std ?? ''}${s.wtp ?? ''}`;
|
|
</script>
|
|
|
|
<section class="departure-board">
|
|
<div class="header container">
|
|
<div class="header row">
|
|
<div class="upper-header-blank"></div>
|
|
<div class="upper-header-text">Arr</div>
|
|
<div class="upper-header-text">Dep</div>
|
|
</div>
|
|
<div class="header row">
|
|
<div class="cell">ID</div>
|
|
<div class="cell">Orig</div>
|
|
<div class="cell">Dest</div>
|
|
<div class="cell">Plt</div>
|
|
<div class="cell">Sch</div>
|
|
<div class="cell">Act</div>
|
|
<div class="cell">Sch</div>
|
|
<div class="cell">Act</div>
|
|
</div>
|
|
</div>
|
|
<div class="services">
|
|
{#each services as service (getRowKey(service))}
|
|
{@const realArrival = formatUkTime(service.ata || service.eta)}
|
|
{@const realDeparture = formatUkTime(service.atd || service.etd)}
|
|
{@const realPass = formatUkTime(service.atp || service.etp)}
|
|
<div
|
|
class="service-block"
|
|
animate:flip={{ duration: 400 }}
|
|
transition:slide={{ duration: 300 }}
|
|
>
|
|
<div
|
|
class="service row"
|
|
class:passing={service.wtp}
|
|
class:nonPax={!service.ip}
|
|
class:cancelled={service.c}
|
|
>
|
|
<div class="cell id">{service.h}</div>
|
|
<div class="cell orig">
|
|
{#key service.og.t}
|
|
<div transition:fade={{ duration: 300 }}>
|
|
{service.og.t}
|
|
</div>
|
|
{/key}
|
|
</div>
|
|
<div class="cell dest">
|
|
{#key service.dt.t}<div transition:fade={{ duration: 300 }}>{service.dt.t}</div>{/key}
|
|
</div>
|
|
<div class="cell plt" class:platsup={service.ps} class:platchange={service.pc}>
|
|
{#key service.p}<div transition:fade={{ duration: 300 }}>{service.p || '-'}</div>{/key}
|
|
</div>
|
|
{#if service.wtp}
|
|
<div class="cell pass">PASS</div>
|
|
<div class="cell sch">
|
|
{formatUkTime(service.wtp)}
|
|
</div>
|
|
<div
|
|
class="cell act {estClass(service.atp, service.etp)} {delayClassFromTimePair(service.wtp, service.atp || service.etp)}"
|
|
>
|
|
{#if isRightTime(service.wtp, service.atp || service.etp)}
|
|
RT
|
|
{:else if service.c}
|
|
CANC
|
|
{:else}
|
|
{realPass}
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<div class="cell sch">{formatUkTime(service.sta)}</div>
|
|
<div
|
|
class="cell act {estClass(service.ata, service.eta)} {delayClassFromTimePair(service.sta, service.ata || service.eta)}"
|
|
>
|
|
{#if isRightTime(service.sta, service.ata || service.eta)}
|
|
RT
|
|
{:else if service.sta && service.c} CANC {:else}
|
|
{realArrival}
|
|
{/if}
|
|
</div>
|
|
<div class="cell sch">{formatUkTime(service.std)}</div>
|
|
<div class="cell act {estClass(service.atd, service.etd)} {delayClassFromTimePair(service.std, service.atd || service.etd)}">
|
|
{#if isRightTime(service.std, service.atd || service.etd)}
|
|
RT
|
|
{:else if service.std && service.c} CANC {:else}
|
|
{realDeparture}
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
<div class="operator row">{service.o}</div>
|
|
{#if service?.cr?.r}
|
|
{#if service.c}
|
|
<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}
|
|
<div class="delay-reason row" transition:slide={{ duration: 300 }}>{service.dr.r}</div>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
|
|
<style>
|
|
.departure-board {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
height: 100%;
|
|
max-height: 100%;
|
|
margin: 0 auto;
|
|
box-sizing: border-box;
|
|
overscroll-behavior-y: contain;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.header.container {
|
|
position: sticky;
|
|
padding-top: 7px;
|
|
top: 0;
|
|
z-index: 10;
|
|
background: var(--color-bg-dark);
|
|
}
|
|
|
|
.row.header {
|
|
font-weight: 875;
|
|
}
|
|
.row.header:last-child {
|
|
padding-bottom: 1rem;
|
|
}
|
|
|
|
.upper-header-blank {
|
|
grid-column: span 4;
|
|
}
|
|
.upper-header-text {
|
|
grid-column: span 2;
|
|
text-align: center;
|
|
}
|
|
|
|
.row {
|
|
display: grid;
|
|
grid-template-columns: 10% 17% 17% 9% 12% 12% 12% 11%;
|
|
align-items: center;
|
|
text-align: center;
|
|
gap: 0;
|
|
box-sizing: border-box;
|
|
font-family: 'Inconsolata Variable', monospace;
|
|
font-size: clamp(1rem, 0.475rem + 2.8vw, 1.35rem);
|
|
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 {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
padding-bottom: 7px;
|
|
}
|
|
|
|
/* Cancellation Logic */
|
|
.service.cancelled,
|
|
.service.cancelled .cell.orig,
|
|
.service.cancelled .cell.dest {
|
|
color: rgb(255, 131, 131);
|
|
}
|
|
.service.cancelled .cell.plt,
|
|
.service.cancelled .cell.act {
|
|
text-decoration: line-through;
|
|
opacity: 0.25;
|
|
}
|
|
.service.cancelled .cell.sch {
|
|
text-decoration: line-through;
|
|
}
|
|
|
|
/* Meta Row Styles */
|
|
.operator.row,
|
|
.delay-reason.row,
|
|
.cancel-reason.row {
|
|
grid-template-columns: 1fr;
|
|
text-align: left;
|
|
font-size: 0.88rem;
|
|
font-stretch: 80%;
|
|
}
|
|
|
|
.cancel-reason.row {
|
|
color: rgb(255, 131, 131);
|
|
font-weight: 400;
|
|
letter-spacing: -0.1ch;
|
|
}
|
|
|
|
.delay-reason.row {
|
|
color: var(--delay-orange);
|
|
font-weight: 400;
|
|
letter-spacing: -0.1ch;
|
|
}
|
|
@media (min-width: 375px) {
|
|
.operator.row,
|
|
.delay-reason.row,
|
|
.cancel-reason.row {
|
|
font-size: 1.08rem;
|
|
}
|
|
}
|
|
@media (min-width: 420px) {
|
|
.operator.row,
|
|
.delay-reason.row,
|
|
.cancel-reason.row {
|
|
font-size: 1.12rem;
|
|
}
|
|
}
|
|
@media (min-width: 550px) {
|
|
.operator.row,
|
|
.delay-reason.row,
|
|
.cancel-reason.row {
|
|
font-size: 1.18rem;
|
|
}
|
|
}
|
|
@media (min-width: 620px) {
|
|
.operator.row,
|
|
.delay-reason.row,
|
|
.cancel-reason.row {
|
|
font-size: 1.2rem;
|
|
}
|
|
}
|
|
|
|
.operator.row {
|
|
font-stretch: 110%;
|
|
padding: 0;
|
|
color: rgb(187, 187, 255);
|
|
}
|
|
|
|
/* Column Specifics */
|
|
.cell.id {
|
|
text-align: left;
|
|
font-weight: 400;
|
|
font-stretch: 85%;
|
|
filter: brightness(0.75);
|
|
}
|
|
@media (min-width: 400px) {
|
|
.cell.id {
|
|
font-stretch: 90%;
|
|
}
|
|
}
|
|
.cell.orig,
|
|
.cell.dest {
|
|
font-stretch: 80%;
|
|
font-weight: 400;
|
|
}
|
|
.cell.orig {
|
|
text-align: left;
|
|
color: var(--location-yellow);
|
|
}
|
|
.cell.dest {
|
|
text-align: right;
|
|
color: var(--location-yellow);
|
|
}
|
|
@media (min-width: 350px) {
|
|
.cell.orig,
|
|
.cell.dest {
|
|
font-stretch: 85%;
|
|
}
|
|
@media (min-width: 400px) {
|
|
.cell.orig,
|
|
.cell.dest {
|
|
font-stretch: 90%;
|
|
}
|
|
}
|
|
@media (min-width: 490px) {
|
|
.cell.orig,
|
|
.cell.dest {
|
|
font-stretch: 95%;
|
|
}
|
|
}
|
|
@media (min-width: 520px) {
|
|
.cell.orig,
|
|
.cell.dest {
|
|
font-stretch: 100%;
|
|
}
|
|
.cell.orig {
|
|
text-align: right;
|
|
padding-right: 7px;
|
|
}
|
|
.cell.dest {
|
|
text-align: left;
|
|
padding-left: 7px;
|
|
}
|
|
}
|
|
@media (min-width: 600px) {
|
|
.cell.orig,
|
|
.cell.dest {
|
|
letter-spacing: 0.05ch;
|
|
}
|
|
}
|
|
}
|
|
.cell.plt {
|
|
text-align: center;
|
|
font-weight: 410;
|
|
font-stretch: 100%;
|
|
}
|
|
@media (min-width: 525px) {
|
|
.cell.plt {
|
|
font-stretch: 110%;
|
|
}
|
|
}
|
|
.cell.plt.platsup {
|
|
font-weight: 150;
|
|
}
|
|
.cell.plt.platchange {
|
|
animation: fast-pulse 2s ease-out infinite;
|
|
}
|
|
.pass-cell {
|
|
text-align: center;
|
|
font-stretch: 100%;
|
|
}
|
|
.cell.pass {
|
|
grid-column: span 2;
|
|
}
|
|
.cell.sch,
|
|
.cell.act {
|
|
text-align: center;
|
|
font-stretch: 72%;
|
|
}
|
|
.cell.act.delay-late {
|
|
color: var(--delay-orange);
|
|
}
|
|
.cell.act.delay-early {
|
|
color: var(--early-blue);
|
|
}
|
|
@media (min-width: 350px) {
|
|
.cell.sch,
|
|
.cell.act {
|
|
font-stretch: 77%;
|
|
}
|
|
}
|
|
@media (min-width: 400px) {
|
|
.cell.sch,
|
|
.cell.act {
|
|
font-stretch: 82%;
|
|
}
|
|
}
|
|
@media (min-width: 525px) {
|
|
.cell.sch,
|
|
.cell.act {
|
|
font-stretch: 90%;
|
|
}
|
|
}
|
|
@media (min-width: 600px) {
|
|
.cell.sch,
|
|
.cell.act {
|
|
font-stretch: 100%;
|
|
}
|
|
}
|
|
.cell.act.est {
|
|
font-style: italic;
|
|
font-weight: 300;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* Time Types */
|
|
.cell.act.estimate {
|
|
font-style: italic;
|
|
opacity: 0.75;
|
|
font-weight: 350;
|
|
}
|
|
.cell.act.actual {
|
|
font-weight: 600;
|
|
}
|
|
</style>
|