Kenneth Bruen
2 years ago
7 changed files with 613 additions and 1 deletions
@ -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> |
@ -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() |
||||
}) |
||||
}) |
@ -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; |
||||
} |
@ -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> |
@ -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…
Reference in new issue