Add station arrivals/departures pages
This commit is contained in:
parent
e4b8a25b60
commit
35b19ffe80
7 changed files with 613 additions and 1 deletions
|
@ -20,7 +20,7 @@
|
|||
<ul>
|
||||
<li><a class="items disabled" href="">Train routes</a></li>
|
||||
<li><a class="items" href="train.html">My train</a></li>
|
||||
<li><a class="items disabled" href="station.html">Station departures/arrivals</a></li>
|
||||
<li><a class="items" href="station.html">Station departures/arrivals</a></li>
|
||||
<li><a class="items" href="about.html">About</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
35
station.html
Normal file
35
station.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<!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>Station - InfoTren</title>
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<link rel="stylesheet" href="/base.css">
|
||||
|
||||
<script src="/common/worker.js"></script>
|
||||
<script src="/common/back.js"></script>
|
||||
<script src="/common/items.js"></script>
|
||||
<script src="station.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Station Information</h1>
|
||||
|
||||
<h4>Station Name</h4>
|
||||
<input class="items" type="tel" 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="rsk"></div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
211
station.js
Executable file
211
station.js
Executable file
|
@ -0,0 +1,211 @@
|
|||
var knownStations = []
|
||||
|
||||
function goToStation(station) {
|
||||
var url = new URL(window.location.href)
|
||||
url.pathname = 'view-station.html'
|
||||
url.searchParams.set('station', station)
|
||||
url.searchParams.set('date', new Date().toISOString())
|
||||
window.location.href = url.toString()
|
||||
}
|
||||
|
||||
function searchNormalize(str) {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replace('ă', 'a')
|
||||
.replace('â', 'a')
|
||||
.replace('î', 'i')
|
||||
.replace('ș', 's')
|
||||
.replace('ț', '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)
|
||||
}
|
||||
|
||||
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 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())
|
||||
}
|
||||
})
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
fetch('https://scraper.infotren.dcdev.ro/v3/stations')
|
||||
.then(function (response) {
|
||||
return response.json()
|
||||
})
|
||||
.then(function (response) {
|
||||
knownStations = response
|
||||
knownStations.sort(function(a, b) { return b.stoppedAtBy.length - a.stoppedAtBy.length })
|
||||
})
|
||||
.then(function () {
|
||||
rebuildSuggestions()
|
||||
})
|
||||
})
|
8
sw.js
8
sw.js
|
@ -16,6 +16,7 @@ self.addEventListener('install', (event) => {
|
|||
'/common/worker.js',
|
||||
'/common/items.js',
|
||||
'/common/back.js',
|
||||
'/common/tabs.js',
|
||||
|
||||
// Base
|
||||
'/base.css',
|
||||
|
@ -32,6 +33,13 @@ self.addEventListener('install', (event) => {
|
|||
'/view-train.js',
|
||||
'/view-train.css',
|
||||
|
||||
'/station.html',
|
||||
'/station.js',
|
||||
|
||||
'/view-station.html',
|
||||
'/view-station.js',
|
||||
'/view-station.css',
|
||||
|
||||
// API
|
||||
API_TRAINS,
|
||||
API_STATIONS,
|
||||
|
|
115
view-station.css
Normal file
115
view-station.css
Normal file
|
@ -0,0 +1,115 @@
|
|||
.IR, .IRN {
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
.early {
|
||||
color: green !important;
|
||||
}
|
||||
|
||||
.late {
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
#-date {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#date {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
#tabs-arr {
|
||||
border-bottom-color: #55ff55;
|
||||
}
|
||||
|
||||
#tabs-dep {
|
||||
border-bottom-color: #5555ff;
|
||||
}
|
||||
|
||||
#arrivals .train-item {
|
||||
background-color: #fafffa;
|
||||
}
|
||||
|
||||
#arrivals .train-item:nth-of-type(even) {
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
#departures .train-item {
|
||||
background-color: #fafaff;
|
||||
}
|
||||
|
||||
#departures .train-item:nth-of-type(even) {
|
||||
background-color: #eaeaff;
|
||||
}
|
||||
|
||||
.train-item.cancelled {
|
||||
background-color: #ffeaea !important;
|
||||
}
|
||||
|
||||
.train-item.cancelled + .train-item.cancelled {
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
|
||||
.train-item {
|
||||
display: grid;
|
||||
grid-template-columns: 30px 60px auto 1fr auto;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:
|
||||
"rank train time terminus platform"
|
||||
"rank train delay terminus platform"
|
||||
"status status status status status";
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
.train-item > * {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.train-item .time {
|
||||
grid-area: time;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.train-item .train {
|
||||
grid-area: train;
|
||||
}
|
||||
|
||||
.train-item .rank {
|
||||
grid-area: rank;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.train-item .delay {
|
||||
grid-area: delay;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.train-item .terminus {
|
||||
grid-area: terminus;
|
||||
}
|
||||
|
||||
.train-item .status {
|
||||
grid-area: status;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.train-item .platform {
|
||||
grid-area: platform;
|
||||
border: 1px solid black;
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
border-radius: 5px;
|
||||
aspect-ratio: 1 / 1;
|
||||
min-width: 22px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
44
view-station.html
Normal file
44
view-station.html
Normal file
|
@ -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>View Station - InfoTren</title>
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<link rel="stylesheet" href="/base.css">
|
||||
<link rel="stylesheet" href="view-station.css">
|
||||
|
||||
<script src="/common/worker.js"></script>
|
||||
<script src="/common/back.js"></script>
|
||||
<script src="/common/items.js"></script>
|
||||
<script src="/common/tabs.js"></script>
|
||||
<script src="view-station.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="title">View Station</h1>
|
||||
<div id="-date" class="hidden">
|
||||
<p></p>
|
||||
<p class="sec" id="date"></p>
|
||||
</div>
|
||||
|
||||
<div class="tabs">
|
||||
<h3 id="tabs-arr">Arrivals</h3>
|
||||
<h3 id="tabs-dep">Departures</h3>
|
||||
</div>
|
||||
|
||||
<div id="arrivals" class="tab-view content">
|
||||
</div>
|
||||
|
||||
<div id="departures" class="tab-view content">
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="lsk"></div>
|
||||
<div class="csk"></div>
|
||||
<div class="rsk">Refresh</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
199
view-station.js
Normal file
199
view-station.js
Normal file
|
@ -0,0 +1,199 @@
|
|||
var station
|
||||
var date
|
||||
|
||||
var stationData = null
|
||||
var lastSuccessfulFetch = null
|
||||
|
||||
function onStationData(data) {
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
|
||||
var title = document.getElementById('title')
|
||||
title.textContent = data.stationName
|
||||
|
||||
document.getElementById('date').textContent = data.date
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} elem
|
||||
* @param {any[]} trains
|
||||
*/
|
||||
function addTrains(elem, trains) {
|
||||
while (elem.childNodes.length > 0) {
|
||||
elem.childNodes[0].remove()
|
||||
}
|
||||
|
||||
var trainsList = document.createElement('ul')
|
||||
elem.appendChild(trainsList)
|
||||
|
||||
trains.forEach(function (train, tIdx) {
|
||||
var trainItem = document.createElement('li')
|
||||
trainsList.appendChild(trainItem)
|
||||
trainItem.classList.add('train-item')
|
||||
if (train.status && train.status.cancelled) {
|
||||
trainItem.classList.add('cancelled')
|
||||
}
|
||||
|
||||
var timeDiv = document.createElement('p')
|
||||
trainItem.appendChild(timeDiv)
|
||||
timeDiv.classList.add('pri', 'time')
|
||||
timeDiv.textContent = new Date(train.time).toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
|
||||
|
||||
if (train.status && train.status.delay != 0) {
|
||||
var delayDiv = document.createElement('p')
|
||||
trainItem.appendChild(delayDiv)
|
||||
delayDiv.classList.add('thi', 'delay')
|
||||
delayDiv.textContent = `${train.status.delay} min `
|
||||
// delayDiv.appendChild(document.createElement('br'))
|
||||
var descSpan = document.createElement('span')
|
||||
delayDiv.appendChild(descSpan)
|
||||
if (train.status.delay > 0) {
|
||||
descSpan.classList.add('late')
|
||||
descSpan.textContent = 'late'
|
||||
}
|
||||
else {
|
||||
descSpan.classList.add('early')
|
||||
descSpan.textContent = 'early'
|
||||
}
|
||||
}
|
||||
|
||||
var rankDiv = document.createElement('p')
|
||||
trainItem.appendChild(rankDiv)
|
||||
rankDiv.textContent = train.train.rank
|
||||
rankDiv.classList.add('sec', 'rank', train.train.rank)
|
||||
|
||||
var trainDiv = document.createElement('p')
|
||||
trainItem.appendChild(trainDiv)
|
||||
trainDiv.classList.add('pri', 'train')
|
||||
trainDiv.appendChild(document.createTextNode(`${train.train.number}`))
|
||||
|
||||
var terminusDiv = document.createElement('p')
|
||||
trainItem.appendChild(terminusDiv)
|
||||
terminusDiv.classList.add('pri', 'terminus')
|
||||
terminusDiv.textContent = train.train.terminus
|
||||
|
||||
if (train.status && train.status.platform) {
|
||||
var platformDiv = document.createElement('div')
|
||||
trainItem.appendChild(platformDiv)
|
||||
platformDiv.classList.add('thi', 'platform')
|
||||
platformDiv.textContent = train.status.platform
|
||||
}
|
||||
|
||||
if (train.status && train.status.cancelled) {
|
||||
var statusDiv = document.createElement('p')
|
||||
trainItem.appendChild(statusDiv)
|
||||
statusDiv.classList.add('sec', 'status')
|
||||
statusDiv.textContent = 'This train is cancelled'
|
||||
}
|
||||
})
|
||||
}
|
||||
addTrains(document.getElementById('arrivals'), data.arrivals)
|
||||
addTrains(document.getElementById('departures'), data.departures)
|
||||
}
|
||||
|
||||
var refreshStopToken = null
|
||||
function refresh() {
|
||||
function reschedule(timeout) {
|
||||
if (refreshStopToken != null) {
|
||||
clearTimeout(refreshStopToken)
|
||||
}
|
||||
refreshStopToken = setTimeout(function () {
|
||||
refresh()
|
||||
}, timeout || 90000)
|
||||
}
|
||||
return fetch(
|
||||
`https://scraper.infotren.dcdev.ro/v3/stations/${station}?date=${date.getFullYear().toString()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`,
|
||||
{
|
||||
cache: 'no-store',
|
||||
},
|
||||
).then(function (response) {
|
||||
if (!response.ok) {
|
||||
// Check in 10 seconds if server returned error
|
||||
reschedule(10000)
|
||||
return
|
||||
}
|
||||
return response.json()
|
||||
}).then(function (response) {
|
||||
if (!response) {
|
||||
return
|
||||
}
|
||||
stationData = response
|
||||
onStationData(response)
|
||||
reschedule()
|
||||
}).catch(function (e) {
|
||||
// Check in 1 second if network error
|
||||
reschedule(1000)
|
||||
throw e
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('unload', function (e) {
|
||||
if (refreshStopToken != null) {
|
||||
clearTimeout(refreshStopToken)
|
||||
}
|
||||
})
|
||||
|
||||
function rsk() {
|
||||
refresh()
|
||||
}
|
||||
|
||||
window.addEventListener('load', function (e) {
|
||||
if (!new URL(window.location.href).searchParams.has('station')) {
|
||||
window.history.back()
|
||||
this.setTimeout(function () {
|
||||
var url = new URL(window.location.href)
|
||||
url.pathname = 'station.html'
|
||||
window.location.href = url.toString()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
var sp = new URL(window.location.href).searchParams
|
||||
|
||||
station = sp.get('station')
|
||||
date = sp.has('date') ? new Date(sp.get('date')) : new Date()
|
||||
|
||||
// View departures first
|
||||
selectedTab = 1
|
||||
selectTab(selectedTab)
|
||||
|
||||
document.querySelectorAll('.rsk').forEach(function (rskElem) {
|
||||
rskElem.addEventListener('click', function (e) {
|
||||
rsk()
|
||||
})
|
||||
})
|
||||
|
||||
refresh()
|
||||
|
||||
setInterval(function () {
|
||||
if (!lastSuccessfulFetch) {
|
||||
return
|
||||
}
|
||||
var millis = new Date() - lastSuccessfulFetch
|
||||
var secs = Math.floor(millis / 1000)
|
||||
|
||||
var timeStr = ''
|
||||
if (secs / 3600 >= 1) {
|
||||
timeStr += `${Math.floor(secs / 3600)}h`
|
||||
secs = secs % 3600
|
||||
}
|
||||
if (secs / 60 >= 1) {
|
||||
timeStr += `${Math.floor(secs / 60)}m`
|
||||
secs = secs % 60
|
||||
}
|
||||
if (secs >= 1) {
|
||||
timeStr += `${Math.floor(secs)}s`
|
||||
}
|
||||
if (!timeStr) {
|
||||
document.querySelectorAll('.lsk').forEach(function (elem) {
|
||||
elem.textContent = 'Last refreshed now'
|
||||
elem.classList.add('last-refreshed')
|
||||
})
|
||||
}
|
||||
else {
|
||||
document.querySelectorAll('.lsk').forEach(function (elem) {
|
||||
elem.textContent = `Last refreshed ${timeStr} ago`
|
||||
elem.classList.add('last-refreshed')
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
})
|
Loading…
Add table
Reference in a new issue