287 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Svelte
		
	
	
	
	
	
			
		
		
	
	
			287 lines
		
	
	
		
			7.1 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>
 |