Kenneth Bruen
2 years ago
5 changed files with 578 additions and 2 deletions
@ -0,0 +1,60 @@
|
||||
.itinerary-train { |
||||
display: grid; |
||||
grid-template-columns: auto 1fr; |
||||
grid-template-rows: repeat(auto-fit, auto); |
||||
} |
||||
|
||||
.itinerary-train:not(:last-child) { |
||||
grid-template-areas: |
||||
"dep-time dep-station" |
||||
"train train" |
||||
"arr-time arrdep-station" |
||||
"dep2-time arrdep-station"; |
||||
} |
||||
|
||||
.itinerary-train:last-child { |
||||
grid-template-areas: |
||||
"train train" |
||||
"arr-time arr-station"; |
||||
} |
||||
|
||||
.itinerary-train:only-child { |
||||
grid-template-areas: |
||||
"dep-time dep-station" |
||||
"train train" |
||||
"arr-time arr-station"; |
||||
} |
||||
|
||||
.itinerary-train .departure.time { |
||||
grid-area: dep-time; |
||||
} |
||||
|
||||
.itinerary-train .next-departure.time { |
||||
grid-area: dep2-time; |
||||
} |
||||
|
||||
.itinerary-train .departure.station { |
||||
grid-area: dep-station; |
||||
} |
||||
|
||||
.itinerary-train .train { |
||||
grid-area: train; |
||||
} |
||||
|
||||
.itinerary-train .arrival.time { |
||||
grid-area: arr-time; |
||||
} |
||||
|
||||
.itinerary-train:not(:last-child) .arrival.station { |
||||
grid-area: arrdep-station; |
||||
align-self: center; |
||||
} |
||||
|
||||
.itinerary-train .arrival.station { |
||||
grid-area: arr-station; |
||||
} |
||||
|
||||
.itinerary-train .time { |
||||
margin-left: 2px; |
||||
margin-right: 2px; |
||||
} |
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>Route - InfoTren</title> |
||||
|
||||
<link rel="manifest" href="/manifest.json"> |
||||
|
||||
<link rel="stylesheet" href="/base.css"> |
||||
<link rel="stylesheet" href="route.css"> |
||||
|
||||
<script src="/common/worker.js"></script> |
||||
<script defer src="/common/back.js"></script> |
||||
<script defer src="/common/items.js"></script> |
||||
<script defer src="/common/trainId.js"></script> |
||||
<script defer src="route.js"></script> |
||||
</head> |
||||
<body> |
||||
<header> |
||||
<div class="left"> |
||||
<img id="back-button" class="back" src="/common/back.svg"></img> |
||||
</div> |
||||
<h1>Find Route</h1> |
||||
<div class="right"></div> |
||||
</header> |
||||
|
||||
<!-- <h4><label for="stationName">Station Name</label></h4>--> |
||||
<!-- <input type="search" class="items" name="stationName" id="stationName">--> |
||||
|
||||
<!-- <h4>Suggestions</h4>--> |
||||
<!-- <div class="content">--> |
||||
<!-- <ul id="suggestionsArea"></ul>--> |
||||
<!-- </div>--> |
||||
|
||||
<footer> |
||||
<div class="lsk"></div> |
||||
<!-- <div class="csk">Search</div>--> |
||||
<div class="csk"></div> |
||||
<div class="rsk"></div> |
||||
</footer> |
||||
</body> |
||||
</html> |
@ -0,0 +1,466 @@
|
||||
/** |
||||
* @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() |
||||
} |
||||
}) |
||||
}) |
Loading…
Reference in new issue