/** * @type {string | null} */ var fromStation = null /** * @type {string | null} */ var toStation = null /** * @type {Date | null} */ var departureDate = null /** * @type {{ name: string, stoppedAtBy: string[] }[]} */ var knownStations = [] function goToStation(station) { var url = new URL(window.location.href) if (!fromStation) { url.searchParams.set('from', station) } else if (!toStation) { url.searchParams.set('to', station) } // url.searchParams.set('date', new Date().toISOString()) window.location.href = url.toString() } function setDepartureDate(departureDate) { var url = new URL(window.location.href) url.searchParams.set('departureDate', departureDate.toISOString()) window.location.href = url.toString() } function searchNormalize(str) { return str .toLowerCase() .replaceAll('ă', 'a') .replaceAll('â', 'a') .replaceAll('î', 'i') .replaceAll('ș', 's') .replaceAll('ț', 't') } var focusedElement = null var _rebuildDebounce = null var _rebuildRequested = false function rebuildSuggestions() { if (_rebuildDebounce !== null) { _rebuildRequested = true return } _rebuildRequested = false _rebuildDebounce = 123 var suggestionsArea = document.getElementById('suggestionsArea') while (suggestionsArea.childNodes.length > 0) { suggestionsArea.childNodes[0].remove() } var stationNameInput = document.getElementById('stationName') var stationName = searchNormalize(stationNameInput.value.trim()) var suggestions = [] if (!stationName) { suggestions = knownStations.slice() } else { for (var i = 0; i < knownStations.length; i++) { if (!searchNormalize(knownStations[i].name).includes(stationName)) { continue } suggestions.push(knownStations[i]) } suggestions.sort((s1, s2) => { var s1n = searchNormalize(s1.name); var s2n = searchNormalize(s2.name); if (s1n.indexOf(stationName) != s2n.indexOf(stationName)) { return s1n.indexOf(stationName) - s2n.indexOf(stationName); } if (s1.stoppedAtBy.length != s2.stoppedAtBy.length) { return s2.stoppedAtBy.length - s1.stoppedAtBy.length; } return s1.name.localeCompare(s2.name); }) } var foundInput = false suggestions.forEach(function (suggestion, index) { if (stationName == searchNormalize(suggestion.name)) { foundInput = true } var suggestionLi = document.createElement('li') suggestionsArea.appendChild(suggestionLi) setTimeout(function () { suggestionLi.classList.add('items') suggestionLi.tabIndex = index + 1 suggestionLi.style.padding = '2px 0' function onAction(e) { goToStation(suggestion.name) } suggestionLi.addEventListener('click', onAction) suggestionLi.addEventListener('keypress', function (e) { if (e.key == 'Enter') { onAction(e) } }) suggestionLi.addEventListener('focus', function (e) { focusedElement = suggestionLi }) var stationNameP = document.createElement('p') suggestionLi.appendChild(stationNameP) stationNameP.textContent = suggestion.name stationNameP.classList.add('pri', 'stationName') // var trainCompanyP = document.createElement('p') // suggestionLi.appendChild(trainCompanyP) // trainCompanyP.textContent = suggestion.company // trainCompanyP.classList.add('thi') }, 0) }) if (!foundInput && stationName) { var suggestionLi = document.createElement('li') suggestionsArea.appendChild(suggestionLi) suggestionLi.classList.add('items') suggestionLi.tabIndex = suggestions.length + 2 suggestionLi.style.padding = '2px 0' function onAction(e) { goToStation(stationNameInput.value.trim()) } suggestionLi.addEventListener('click', onAction) suggestionLi.addEventListener('keypress', function (e) { if (e.key == 'Enter') { onAction(e) } }) suggestionLi.addEventListener('focus', function (e) { focusedElement = suggestionLi }) var stationNameP = document.createElement('p') suggestionLi.appendChild(stationNameP) stationNameP.textContent = stationNameInput.value.trim() stationNameP.classList.add('pri', 'stationName') } setTimeout(function () { _rebuildDebounce = null if (_rebuildRequested) { rebuildSuggestions() } }, 500) } /** * @typedef ItineraryTrain * @property {string} from * @property {string} to * @property {string[]} intermediateStops * @property {string} departureDate * @property {string} arrivalDate * @property {number} km * @property {string} operator * @property {string} trainRank * @property {string} trainNumber */ /** * @typedef Itinerary * @property {ItineraryTrain[]} trains */ /** * @param {Itinerary[]} data */ function onItineraries(data) { var contentDiv = document.createElement('div') document.body.insertBefore(contentDiv, document.querySelector('footer')) contentDiv.classList.add('content') for (var i = 0; i < data.length; i++) { var itineraryDiv = document.createElement('div') contentDiv.appendChild(itineraryDiv) var heading = document.createElement('h4') itineraryDiv.appendChild(heading) heading.textContent = `Itinerary ${i + 1}` var trainsDiv = document.createElement('div') itineraryDiv.appendChild(trainsDiv) trainsDiv.classList.add('itinerary-trains') data[i].trains.forEach(function (train, idx) { var last = idx === data[i].trains.length - 1 var trainDiv = document.createElement('div') trainsDiv.appendChild(trainDiv) trainDiv.classList.add('itinerary-train') if (idx === 0) { var departureTimeP = document.createElement('p') trainDiv.appendChild(departureTimeP) departureTimeP.classList.add('sec', 'departure', 'time') var departureTimePre = document.createElement('pre') departureTimeP.appendChild(departureTimePre) var departure = new Date(train.departureDate) departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) var departureHeading = document.createElement('h3') trainDiv.appendChild(departureHeading) departureHeading.classList.add('departure', 'station') var departureLink = document.createElement('a') departureHeading.appendChild(departureLink) departureLink.textContent = train.from departureLink.classList.add('no-custom-a', 'items') var departureUrl = new URL('/view-station.html', window.location.origin) departureUrl.searchParams.set('station', train.from) departureLink.href = departureUrl.toString() } var trainP = document.createElement('p') trainDiv.appendChild(trainP) trainP.classList.add('pri', 'train') var trainLink = document.createElement('a') trainP.appendChild(trainLink) trainIdSpan(train.trainRank, train.trainNumber, trainLink) trainLink.classList.add('no-custom-a', 'items') var trainUrl = new URL('/view-train.html', window.location.origin) trainUrl.searchParams.set('train', train.trainNumber) trainLink.href = trainUrl.toString() trainP.appendChild(document.createTextNode(' ')) var trainCompany = document.createElement('span') trainP.appendChild(trainCompany) trainCompany.textContent = '(' + train.operator + ')' trainCompany.classList.add('company') var arrivalTimeP = document.createElement('p') trainDiv.appendChild(arrivalTimeP) arrivalTimeP.classList.add('sec', 'arrival', 'time') var arrivalTimePre = document.createElement('pre') arrivalTimeP.appendChild(arrivalTimePre) var arrival = new Date(train.arrivalDate) arrivalTimePre.textContent = arrival.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) var arrivalHeading = document.createElement('h3') trainDiv.appendChild(arrivalHeading) arrivalHeading.classList.add('arrival', 'station') var arrivalLink = document.createElement('a') arrivalHeading.appendChild(arrivalLink) arrivalLink.textContent = train.to arrivalLink.classList.add('no-custom-a', 'items') var arrivalUrl = new URL('/view-station.html', window.location.origin) arrivalUrl.searchParams.set('station', train.from) arrivalLink.href = arrivalUrl.toString() if (!last) { var nextDepartureTimeP = document.createElement('p') trainDiv.appendChild(nextDepartureTimeP) nextDepartureTimeP.classList.add('sec', 'next-departure', 'time') var departureTimePre = document.createElement('pre') nextDepartureTimeP.appendChild(departureTimePre) var departure = new Date(data[i].trains[idx + 1].departureDate) departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) } }) } } function lsk() { document.getElementById('stationName').focus() } function csk() { if (focusedElement == null) { return } if (focusedElement.id === 'stationName') { goToTrain(document.activeElement.value.trim()) } else { focusedElement.click() } } window.addEventListener('load', function (e) { var sp = new URL(window.location.href).searchParams fromStation = sp.get('from') toStation = sp.get('to') var departureDateStr = sp.get('departureDate') if (departureDateStr) { departureDate = new Date(departureDateStr) } var titleH1 = document.querySelector("header > h1") if (!fromStation) { titleH1.textContent = 'Find Route - From' } else if (!toStation) { titleH1.textContent = 'Find Route - To' } else if (!departureDate) { titleH1.textContent = 'Find Route - Departure Date' } else { titleH1.textContent = `${fromStation} - ${toStation}` } var footer = document.querySelector('footer') if (!fromStation || !toStation) { // Build station selection UI var stationNameH4 = document.createElement('h4') document.body.insertBefore(stationNameH4, footer) var stationNameLabel = document.createElement('label') stationNameH4.appendChild(stationNameLabel) stationNameLabel.htmlFor = 'stationName' stationNameLabel.textContent = 'Station Name' var stationNameInput = document.createElement('input') document.body.insertBefore(stationNameInput, footer) stationNameInput.type = 'search' stationNameInput.classList.add('items') stationNameInput.name = 'stationName' stationNameInput.id = 'stationName' var suggestionsH4 = document.createElement('h4') document.body.insertBefore(suggestionsH4, footer) suggestionsH4.textContent = 'Suggestions' var contentDiv = document.createElement('div') document.body.insertBefore(contentDiv, footer) contentDiv.classList.add('content') var suggestionsUl = document.createElement('ul') contentDiv.appendChild(suggestionsUl) suggestionsUl.id = 'suggestionsArea' document.querySelector('.csk').textContent = 'Search' var stationName = document.getElementById('stationName') stationName.addEventListener('input', function (e) { rebuildSuggestions() }) stationName.addEventListener('focus', function (e) { focusedElement = stationName document.getElementsByClassName('lsk')[0].textContent = '' document.getElementsByClassName('csk')[0].textContent = 'Search' }) stationName.addEventListener('blur', function (e) { document.getElementsByClassName('lsk')[0].textContent = 'Search' document.getElementsByClassName('csk')[0].textContent = 'Select' }) stationName.addEventListener('keypress', function (e) { if (e.key == 'Enter') { goToStation(stationName.value.trim()) } }) fetch('https://scraper.infotren.dcdev.ro/v3/stations') .then(function (response) { return response.json() }) .then(function (response) { knownStations = response knownStations = knownStations.filter((s) => ![fromStation, toStation].includes(s.name)) knownStations.sort(function(a, b) { return b.stoppedAtBy.length - a.stoppedAtBy.length }) }) .then(function () { rebuildSuggestions() }) } else if (!departureDate) { var departureDateH4 = document.createElement('h4') document.body.insertBefore(departureDateH4, footer) departureDateH4.textContent = 'Departure Date' var contentDiv = document.createElement('div') document.body.insertBefore(contentDiv, footer) contentDiv.classList.add('content') var departureDateUl = document.createElement('ul') contentDiv.appendChild(departureDateUl) departureDateUl.id = 'suggestionsArea' for (var i = 0, departureOption = new Date(); i < 30; i++, departureOption.setDate(departureOption.getDate() + 1)) { var suggestionLi = document.createElement('li') departureDateUl.appendChild(suggestionLi) suggestionLi.classList.add('items') suggestionLi.tabIndex = i + 10 // Capture ;(function () { var d = new Date(departureOption.getTime()) function onAction() { setDepartureDate(d) } suggestionLi.addEventListener('click', onAction) suggestionLi.addEventListener('keypress', function (e) { if (e.key == 'Enter') { onAction(e) } }) suggestionLi.addEventListener('focus', function (e) { focusedElement = suggestionLi }) })() var innerP = document.createElement('p') suggestionLi.appendChild(innerP) innerP.classList.add('pri') var innerPre = document.createElement('pre') innerP.appendChild(innerPre) innerPre.textContent = `${departureOption.getDate().toString().padStart(2, '0')}.${(departureOption.getMonth() + 1).toString().padStart(2, '0')}.${departureOption.getFullYear().toString().padStart(4, '0')}` } document.querySelector('.csk').textContent = 'Select' } else { var contentDiv = document.createElement('div') document.body.insertBefore(contentDiv, footer) contentDiv.classList.add('content') contentDiv.style.display = 'flex' contentDiv.style.flexDirection = 'column' contentDiv.style.alignItems = 'center' contentDiv.style.justifyContent = 'center' var loadingP = document.createElement('p') contentDiv.appendChild(loadingP) loadingP.classList.add('pri') loadingP.textContent = 'Loading data...' var url = new URL('https://scraper.infotren.dcdev.ro/v3/itineraries') url.searchParams.set('from', fromStation) url.searchParams.set('to', toStation) url.searchParams.set('date', departureDate.toISOString()) fetch(url.toString()) .then(function (response) { return response.json() }) .then(function (data) { contentDiv.remove() onItineraries(data) }) .catch(function (e) { loadingP.textContent = 'An error has occured' var errorP = document.createElement('p') contentDiv.appendChild(errorP) errorP.classList.add('sec') errorP.textContent = e.toString() var retryLink = document.createElement('a') contentDiv.appendChild(retryLink) retryLink.classList.add('items') retryLink.textContent = 'Retry' retryLink.href = '' }) } document.querySelectorAll('.lsk').forEach(function (lskElem) { lskElem.addEventListener('click', function (e) { lsk() }) }) document.querySelectorAll('.csk').forEach(function (cskElem) { cskElem.addEventListener('click', function (e) { csk() }) }) document.body.addEventListener('keydown', function (e) { if (e.key == 'SoftLeft') { lsk() } else if (e.key == 'Enter') { csk() } }) })