KaiOS webapp for InfoFer scraper
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
16 KiB

/**
* @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')
departureHeading.textContent = train.from
}
var trainP = document.createElement('p')
trainDiv.appendChild(trainP)
trainP.classList.add('pri', 'train')
trainIdSpan(train.trainRank, train.trainNumber, trainP)
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')
arrivalHeading.textContent = train.to
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()
}
})
})