From a723e56b4ce392d2b11d28f2745279aa825a2ee1 Mon Sep 17 00:00:00 2001 From: memdmp Date: Mon, 21 Jul 2025 22:53:11 +0200 Subject: feat: initial commit --- src/lib/Timetable.svelte | 424 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 src/lib/Timetable.svelte (limited to 'src/lib/Timetable.svelte') diff --git a/src/lib/Timetable.svelte b/src/lib/Timetable.svelte new file mode 100644 index 0000000..4a17ca7 --- /dev/null +++ b/src/lib/Timetable.svelte @@ -0,0 +1,424 @@ + + +{#snippet renderLocalTime( + time: Date, + forceAbsolute = false +)}{#await localTime(time, forceAbsolute, now)}{isRelativeTime && !forceAbsolute + ? timeRelative(time.getTime(), now) + : time.getUTCHours() + + ':' + + time.getUTCMinutes() + + (time.getUTCSeconds() !== 0 ? `:${time.getUTCSeconds()}` : '') + + ' UTC'}{:then t}{t}{/await}{/snippet} + +{#if stopTimes} + {#each stopTimes.stopTimes + // garbage data + .filter((v) => v.agencyUrl !== 'http://www.rta.ae') as departure} + {@const expectedTime = + new Date( + (isArrivals + ? departure.place.scheduledArrival + : departure.place.scheduledDeparture) ?? '0' + ).getTime() / + 1000 / + 60} + {@const receivedTime = + new Date( + (isArrivals ? departure.place.arrival : departure.place.departure) ?? + '0' + ).getTime() / + 1000 / + 60} + {@const delayMinutes = receivedTime - expectedTime} + {@const avoidGlyph = departure.routeShortName.startsWith('FlixTrain')} + {@const routeShortName = (() => { + let n = departure.routeShortName; + if (n.startsWith('EC ')) n = n.replace('EC ', 'EC'); + if (departure.mode === 'TRAM' && !isNaN(parseInt(n))) n = 'T ' + n; + if (departure.mode === 'BUS' && !isNaN(parseInt(n))) n = 'B ' + n; + if ( + departure.routeShortName === 'European Sleeper' && + departure.agencyName === 'Eu Sleeper' + ) + n = 'EN'; // TODO: validate these are real euronights + if (n === '?') n = ''; + if (n.startsWith('FlixTrain ')) n = n.substring(10); + // Note: may also catch ECs/ICs/EXTs operated by DB + if ( + departure.agencyId === '12681' && + departure.agencyName === 'DB Fernverkehr AG' && + departure.mode === 'HIGHSPEED_RAIL' && + departure.source.startsWith('de_DELFI.gtfs.zip/') && + !isNaN(parseInt(n)) + ) + n = `ICE ${n}`; + return n; + })()} + {@const pictogram = (() => { + switch (true) { + case departure.mode === Mode.Bike: + return 'Velo_l'; + case departure.mode === Mode.ODM: + case departure.mode === Mode.Rental: + return 'Taxi_l'; + case departure.mode === Mode.Car: + case departure.mode === Mode.CarDropoff: + case departure.mode === Mode.CarParking: + return 'Auto_l'; + + // Transit // + case departure.mode === Mode.Airplane: + return 'Abflug_l'; + case departure.mode === Mode.LongDistanceRail: + case departure.mode === Mode.RegionalFastRail: + case departure.mode === Mode.RegionalRail: + case departure.mode === Mode.Metro: + case departure.mode === Mode.HighspeedRail: + return 'Zug_l'; + case departure.mode === Mode.NightRail: + return 'Schlafwagen'; + case departure.mode === Mode.Subway: + return 'Metro_l_' + (m.lang_short() === 'en' ? 'de' : m.lang_short()); + case departure.mode === Mode.Bus: + return 'Bus_l'; + case departure.mode === Mode.Coach: + return 'Fernbus_l'; + case departure.mode === Mode.Tram: + case departure.mode === Mode.CableTram: + return 'Tram_l'; + case departure.mode === Mode.Funicular: + return 'Zahnradbahn_l'; + case departure.mode === Mode.AerialLift: + // return 'Gondelbahn_l'; + return 'Luftseilbahn_l'; + case departure.mode === Mode.Ferry: + return 'Schiff_l'; + case departure.mode === Mode.Other: + default: + return 'Licht'; + } + })()} + {@const notices = (() => { + let notices = [] as [pictogram: string[], content: string][]; + if (departure.cancelled) + notices.push([ + ['Cancellation', 'Attention'], + m.brief_north_otter_cherish(), + ]); + if (delayMinutes < -0.5) { + notices.push([ + ['Hint'], + m.vehicle_is_early({ + minutes: -delayMinutes, + arrival: isArrivals.toString(), + }), + ]); + } else if (delayMinutes >= 1) { + notices.push([ + delayMinutes >= 5 + ? ['Delay', 'Attention'] + : delayMinutes >= 3 + ? ['Delay', 'Hint'] + : ['Hint'], + m.next_long_lark_bask({ + minutes: delayMinutes.toFixed(0), + }), + ]); + } + return notices; + })()} + {@const situationIsBad = notices.find((v) => v[0].includes('Attention'))} +
+
+
+ {#if pictogram} + + {/if} + {#if ([Mode.NightRail || Mode.HighspeedRail || Mode.LongDistanceRail || Mode.RegionalFastRail || Mode.RegionalRail].includes(departure.mode) || (departure.mode === 'BUS' && routeShortName.startsWith('EV')) || (departure.mode === 'METRO' && departure.routeShortName.startsWith('S'))) && !avoidGlyph} + + {:else} + + {routeShortName} + + {/if} + + + {m.to()} + {departure.headsign} + +
+
+ {#if departure.place.scheduledTrack && departure.place.track} + + {#if departure.place.name !== placeName}{`${ + departure.place.name === placeName + ', Bahnhof' + ? placeName + ', Busbahnhof' + : departure.place.name + }, `} + {/if}{m.station_location({ + track: departure.place.track, + mode: departure.mode, + })} + + {:else if departure.place.name !== placeName}{departure.place.name === + placeName + ', Bahnhof' + ? placeName + ', Busbahnhof' + : departure.place.name} + {/if} +
+
+ {departure.cancelled + ? m.antsy_weird_cowfish_wish() + ' ' + : ''}{isRelativeTime && Math.abs(expectedTime - receivedTime) < 1 + ? isArrivals + ? m.hour_tidy_hawk_explore() + : m.free_knotty_ray_soar() + : isArrivals + ? m.polite_lucky_angelfish_amaze() + : m.home_flaky_jurgen_ascend()} + {#if Math.abs(expectedTime - receivedTime) < 1} + + {@render renderLocalTime(new Date(receivedTime * 60 * 1000))} + + {:else} + + {@render renderLocalTime(new Date(expectedTime * 60 * 1000), true)} + + + {@render renderLocalTime(new Date(receivedTime * 60 * 1000), true)} + + {#if isRelativeTime} + + ({@render renderLocalTime(new Date(receivedTime * 60 * 1000))}) + + {/if} + {/if} +
+ {#if notices.length !== 0} +
+ {#each notices as notice} +
+ {#each notice[0] as pictogram}{/each} + + {notice[1]} + +
+ {/each} +
+ {/if} + + {#if departure.agencyName} + {m.operated_by({ + operator: operators.has(departure.agencyName) + ? operators.get(departure.agencyName)! + : departure.agencyName, + })}{#if departure.agencyName === 'DB Fernverkehr AG'} + {' '} + · + {m.line_number_accuracy()}{/if} + {/if} +
+ {/each} +{:else} +
+
+ {#if (placeName || placeId) && isResultsPage} +

No results

+

+ No results have been found for the station {placeName ?? placeId}.
+ Please try again. +

+ {:else if placeId} +

No results

+

+ No results have been found for the station {placeName ?? placeId}.
+ Please try again. +

+ {:else} +

No Station

+

+ Please input a station in the search field above and select a search + result. +

+

+ Examples: + + + + + + +

+ {/if} +
+
+{/if} -- cgit v1.2.3