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.
624 lines
18 KiB
624 lines
18 KiB
/** |
|
* @type {string} |
|
*/ |
|
var trainNumber |
|
/** |
|
* @type {Date} |
|
*/ |
|
var date |
|
var groupIndex = null |
|
|
|
var yesterday = false |
|
var showKm = false |
|
|
|
var trainData = null |
|
/** |
|
* @type {?Date} |
|
*/ |
|
var lastSuccessfulFetch = null |
|
|
|
/** |
|
* @typedef ArrDep |
|
* @property {string} scheduleTime |
|
* @property {?{delay: number, real: boolean}} status |
|
* |
|
* @typedef Note |
|
* @property {string} kind |
|
* |
|
* @typedef DepartsAsNote |
|
* @type {Note} |
|
* @property {"departsAs"} kind |
|
* @property {string} rank |
|
* @property {string} number |
|
* |
|
* @typedef TrainNumberChangeNote |
|
* @type {Note} |
|
* @property {"trainNumberChange"} kind |
|
* @property {string} rank |
|
* @property {string} number |
|
* |
|
* @typedef DetachingWagonsNote |
|
* @type {Note} |
|
* @property {"detachingWagons"} kind |
|
* @property {string} station |
|
* |
|
* @typedef ReceivingWagonsNote |
|
* @type {Note} |
|
* @property {"receivingWagons"} kind |
|
* @property {string} station |
|
* |
|
* @typedef TrainStop |
|
* @property {string} name |
|
* @property {number} km |
|
* @property {?number} stoppingTime |
|
* @property {?string} platform |
|
* @property {ArrDep} arrival |
|
* @property {ArrDep} departure |
|
* @property {Note[]} notes |
|
* |
|
* @typedef Group |
|
* @property {{from: string; to: string}} route |
|
* @property {{delay: number; station: string; state: "passing" | "arrival" | "departure"} | undefined} status |
|
* @property {TrainStop[]} stations |
|
*/ |
|
|
|
/** |
|
* @param {{ rank: string; number: string; operator: string; date: string; groups: Group[]; }} data |
|
* @param {?Date} fetchDate |
|
*/ |
|
function onTrainData(data, fetchDate) { |
|
var title = document.getElementById('title') |
|
title.textContent = '' |
|
title.appendChild(document.createTextNode('Train ')) |
|
trainIdSpan(data.rank, data.number, title) |
|
|
|
document.getElementsByTagName('title')[0].textContent = `Train ${data.rank} ${data.number}` |
|
|
|
document.getElementById('company').textContent = data.operator |
|
var dateHref = document.createElement('a') |
|
var dateP = document.getElementById('date') |
|
while (dateP.childNodes.length > 0) { |
|
dateP.childNodes[0].remove() |
|
} |
|
dateP.appendChild(dateHref) |
|
dateHref.textContent = data.date |
|
dateHref.href = '#' |
|
dateHref.classList.add('no-a-custom') |
|
dateHref.classList.add('items', 'no-a-custom') |
|
dateHref.addEventListener('click', function (e) { |
|
e.preventDefault() |
|
// Implement date switcher |
|
yesterday = !yesterday |
|
refresh() |
|
}) |
|
|
|
document.getElementById('loading').classList.add('hidden') |
|
|
|
/** |
|
* @type {Group | null} |
|
*/ |
|
var group = null; |
|
if (data.groups.length > 1 && groupIndex == null) { |
|
document.getElementById('group-choice').classList.remove('hidden') |
|
document.getElementById('group-choice').focus() |
|
document.getElementById('train-info').classList.add('hidden') |
|
|
|
document.getElementsByClassName('rsk')[0].textContent = '' |
|
document.getElementsByClassName('csk')[0].textContent = 'Select' |
|
|
|
title.textContent = 'Select Group for ' |
|
trainIdSpan(data.rank, data.number, title) |
|
|
|
var gc = document.getElementById('group-choice') |
|
while (gc.childNodes.length > 0) { |
|
gc.childNodes[0].remove() |
|
} |
|
|
|
for (var i = 0; i < data.groups.length; i++) { |
|
var g = data.groups[i] |
|
|
|
var groupLi = document.createElement('li') |
|
gc.append(groupLi) |
|
groupLi.tabIndex = i |
|
groupLi.classList.add('items') |
|
if (i === currentIndex) { |
|
groupLi.focus() |
|
} |
|
|
|
(function (i) { |
|
function onAction(e) { |
|
var url = new URL(window.location.toString()) |
|
groupIndex = i |
|
if (window.localStorage) { |
|
localStorage.setItem('recent/view-train', JSON.stringify({ |
|
trainNumber: trainNumber, |
|
date: date.toISOString(), |
|
groupIndex: groupIndex, |
|
$addDate: new Date().toISOString(), |
|
})) |
|
} |
|
url.searchParams.append('groupIndex', groupIndex) |
|
window.history.pushState({'groupIndex': groupIndex}, '', url.toString( )) |
|
onTrainData(data) |
|
} |
|
groupLi.addEventListener('click', onAction) |
|
groupLi.addEventListener('keypress', function (e) { |
|
if (e.key == 'Enter') { |
|
onAction(e) |
|
} |
|
}) |
|
})(i) |
|
|
|
var routeP = document.createElement('p') |
|
groupLi.append(routeP) |
|
routeP.classList.add('pri') |
|
routeP.textContent = `${g.route.from} ➔ ${g.route.to}` |
|
|
|
var groupP = document.createElement('p') |
|
groupLi.append(groupP) |
|
groupP.classList.add('thi') |
|
groupP.textContent = i === 0 ? 'Main train' : `Group ${i}` |
|
|
|
} |
|
|
|
return |
|
} |
|
else if (data.groups.length === 1) { |
|
group = data.groups[0] |
|
} |
|
else { |
|
group = data.groups[groupIndex] |
|
} |
|
document.getElementById('group-choice').classList.add('hidden') |
|
document.getElementById('train-info').classList.remove('hidden') |
|
document.getElementById('train-info').focus() |
|
|
|
document.getElementsByClassName('rsk')[0].textContent = 'Refresh' |
|
document.getElementsByClassName('csk')[0].textContent = '' |
|
|
|
document.getElementById('route-from').textContent = group.route.from |
|
document.getElementById('route-to').textContent = group.route.to |
|
|
|
if (group.status) { |
|
document.getElementById('status').classList.remove('hidden') |
|
|
|
var statusDelay = document.getElementById('status-delay') |
|
while (statusDelay.childNodes.length > 0) { |
|
statusDelay.childNodes[0].remove() |
|
} |
|
var delayString = '' |
|
var delayMinutes = group.status.delay |
|
if (delayMinutes === 0) { |
|
delayString = 'On time' |
|
statusDelay.appendChild(document.createTextNode(delayString)) |
|
} |
|
else { |
|
var early = false |
|
if (delayMinutes < 0) { |
|
early = true |
|
delayMinutes = -delayMinutes |
|
} |
|
|
|
if (delayMinutes >= 60) { |
|
var hours = Math.floor(delayMinutes / 60) |
|
delayMinutes = delayMinutes % 60 |
|
delayString += hours.toString() |
|
delayString += ' hour' |
|
if (hours > 1) { |
|
delayString += 's' |
|
} |
|
} |
|
if (delayMinutes > 0) { |
|
if (delayString.length > 0) { |
|
delayString += ' and ' |
|
} |
|
delayString += delayMinutes.toString() |
|
delayString += ' minute' |
|
if (delayMinutes > 1) { |
|
delayString += 's' |
|
} |
|
} |
|
delayString += ' ' |
|
statusDelay.appendChild(document.createTextNode(delayString)) |
|
|
|
var kindSpan = document.createElement('span') |
|
statusDelay.appendChild(kindSpan) |
|
if (early) { |
|
kindSpan.textContent = 'early' |
|
kindSpan.classList.add('early') |
|
} |
|
else { |
|
kindSpan.textContent = 'late' |
|
kindSpan.classList.add('late') |
|
} |
|
} |
|
|
|
var statusLocation = document.getElementById('status-location') |
|
while (statusLocation.childNodes.length > 0) { |
|
statusLocation.childNodes[0].remove() |
|
} |
|
var stateString = '' |
|
if (group.status.state === 'arrival') { |
|
stateString += 'when arriving at ' |
|
} |
|
else if (group.status.state === 'departure') { |
|
stateString += 'when departing from ' |
|
} |
|
else if (group.status.state === 'passing') { |
|
stateString += 'while passing through ' |
|
} |
|
statusLocation.appendChild(document.createTextNode(stateString)) |
|
var stationSpan = document.createElement('span') |
|
statusLocation.appendChild(stationSpan) |
|
stationSpan.textContent = group.status.station |
|
stationSpan.classList.add('station') |
|
} |
|
else { |
|
document.getElementById('status').classList.add('hidden') |
|
} |
|
|
|
var stationsDiv = document.getElementById('stations') |
|
while (stationsDiv.childNodes.length > 0) { |
|
stationsDiv.childNodes[0].remove() |
|
} |
|
|
|
var separator = document.createElement('h4') |
|
stationsDiv.appendChild(separator) |
|
separator.textContent = 'Stations' |
|
|
|
var stationsList = document.createElement('ul') |
|
stationsDiv.appendChild(stationsList) |
|
|
|
group.stations.forEach(function (station) { |
|
var stationItem = document.createElement('li') |
|
stationsList.appendChild(stationItem) |
|
stationItem.classList.add('stationItem') |
|
|
|
var stationName = document.createElement('p') |
|
stationItem.appendChild(stationName) |
|
stationName.classList.add('pri', 'name') |
|
var stationNameHref = document.createElement('a') |
|
stationName.appendChild(stationNameHref) |
|
stationNameHref.textContent = station.name |
|
stationNameHref.classList.add('items', 'no-a-custom') |
|
var stationUrl = new URL('/view-station.html', window.location.origin) |
|
stationUrl.searchParams.append('station', station.name) |
|
stationUrl.searchParams.append('date', (station.arrival || station.departure).scheduleTime) |
|
stationNameHref.href = stationUrl.toString() |
|
|
|
if (station.arrival) { |
|
var stationArrival = document.createElement('div') |
|
stationItem.appendChild(stationArrival) |
|
stationArrival.classList.add('arrival') |
|
|
|
var originalArr = document.createElement('p') |
|
stationArrival.appendChild(originalArr) |
|
var originalArrSpan = document.createElement('pre') |
|
originalArr.appendChild(originalArrSpan) |
|
var arrDate = new Date(station.arrival.scheduleTime) |
|
originalArrSpan.textContent = arrDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) |
|
originalArr.classList.add('pri') |
|
if (station.arrival.status && station.arrival.status.delay != 0) { |
|
originalArr.classList.remove('pri') |
|
originalArr.classList.add('thi') |
|
originalArrSpan.classList.add('original') |
|
var delaySpanArr = document.createElement('span') |
|
originalArr.appendChild(delaySpanArr) |
|
delaySpanArr.textContent = `${station.arrival.status.delay > 0 ? '+' : ''}${station.arrival.status.delay}`; |
|
delaySpanArr.classList.add(station.arrival.status.delay > 0 ? 'late' : 'early') |
|
delaySpanArr.style.marginLeft = '4px' |
|
|
|
var actualArr = document.createElement('p') |
|
stationArrival.appendChild(actualArr) |
|
arrDate.setMinutes(arrDate.getMinutes() + station.arrival.status.delay) |
|
actualArr.classList.add('pri', station.arrival.status.delay > 0 ? 'late' : 'early') |
|
if (!station.arrival.status.real) { |
|
actualArr.classList.add('not-real') |
|
} |
|
var actualArrPre = document.createElement('pre') |
|
actualArr.appendChild(actualArrPre) |
|
actualArrPre.textContent = arrDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) |
|
} |
|
} |
|
|
|
if (station.departure) { |
|
var stationDeparture = document.createElement('div') |
|
stationItem.appendChild(stationDeparture) |
|
stationDeparture.classList.add('departure') |
|
|
|
var originalDep = document.createElement('p') |
|
stationDeparture.appendChild(originalDep) |
|
var depDate = new Date(station.departure.scheduleTime) |
|
var originalDepSpan = document.createElement('pre') |
|
originalDep.appendChild(originalDepSpan) |
|
originalDepSpan.textContent = depDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) |
|
originalDep.classList.add('pri') |
|
if (station.departure.status && station.departure.status.delay != 0) { |
|
originalDep.classList.remove('pri') |
|
originalDep.classList.add('thi') |
|
originalDepSpan.classList.add('original') |
|
var delaySpanDep = document.createElement('span') |
|
originalDep.appendChild(delaySpanDep) |
|
delaySpanDep.textContent = `${station.departure.status.delay > 0 ? '+' : ''}${station.departure.status.delay}`; |
|
delaySpanDep.classList.add(station.departure.status.delay > 0 ? 'late' : 'early') |
|
delaySpanDep.style.marginLeft = '4px' |
|
|
|
var actualDep = document.createElement('p') |
|
stationDeparture.appendChild(actualDep) |
|
depDate.setMinutes(depDate.getMinutes() + station.departure.status.delay) |
|
actualDep.classList.add('pri', station.departure.status.delay > 0 ? 'late' : 'early') |
|
if (!station.departure.status.real) { |
|
actualDep.classList.add('not-real') |
|
} |
|
var actualDepPre = document.createElement('pre') |
|
actualDep.appendChild(actualDepPre) |
|
actualDepPre.textContent = depDate.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) |
|
} |
|
} |
|
|
|
var stationKm = document.createElement('p') |
|
stationItem.appendChild(stationKm) |
|
stationKm.textContent = `${station.km} km` |
|
stationKm.classList.add('thi', 'km') |
|
if (!showKm) { |
|
stationKm.classList.add('hidden') |
|
} |
|
|
|
if (station.platform) { |
|
var stationPlatform = document.createElement('p') |
|
stationItem.appendChild(stationPlatform) |
|
stationPlatform.textContent = `platform ${station.platform}` |
|
stationPlatform.classList.add('thi', 'platform') |
|
} |
|
|
|
if (station.notes && station.notes.length > 0) { |
|
var stationNotes = document.createElement('div') |
|
stationItem.appendChild(stationNotes) |
|
stationNotes.classList.add('notes') |
|
|
|
station.notes.forEach(function (note) { |
|
var noteP = document.createElement('p') |
|
stationNotes.appendChild(noteP) |
|
noteP.classList.add('note', 'thi') |
|
|
|
switch (note.kind) { |
|
case 'departsAs': { |
|
noteP.textContent = 'Train departs as ' |
|
trainIdSpan(note.rank, note.number, noteP) |
|
break |
|
} |
|
case 'detachingWagons': { |
|
noteP.textContent = `Detaching wagons to ${note.station}` |
|
break |
|
} |
|
case 'receivingWagons': { |
|
noteP.textContent = `Receiving wagons from ${note.station}` |
|
break |
|
} |
|
case 'trainNumberChange': { |
|
noteP.textContent = 'Train changes number to ' |
|
trainIdSpan(note.rank, note.number, noteP) |
|
break |
|
} |
|
} |
|
}) |
|
} |
|
}) |
|
|
|
lastSuccessfulFetch = fetchDate || new Date() |
|
} |
|
|
|
var refreshStopToken = null |
|
|
|
/** |
|
* @returns {Promise<boolean>} |
|
*/ |
|
function refresh() { |
|
function reschedule(timeout) { |
|
if (refreshStopToken != null) { |
|
clearTimeout(refreshStopToken) |
|
} |
|
refreshStopToken = setTimeout(function () { |
|
refresh() |
|
}, timeout || 60000) |
|
} |
|
/** |
|
* @type {Date} |
|
*/ |
|
var reqDate = new Date(date.valueOf()) |
|
if (yesterday) { |
|
reqDate.setDate(reqDate.getDate() - 1) |
|
} |
|
reqDate.setMinutes(0, 0, 0) |
|
return fetch( |
|
`https://scraper.infotren.dcdev.ro/v3/trains/${trainNumber}?date=${reqDate.toISOString()}`, |
|
{ |
|
cache: 'no-store', |
|
}, |
|
).then(function (response) { |
|
if (!response.ok) { |
|
// Check in 10 seconds if server returned error |
|
reschedule(10000) |
|
return |
|
} |
|
var cacheDate = response.headers.get('SW-Cached-At') |
|
return response.json().then(function (data) { |
|
data['$cacheDate'] = cacheDate |
|
return data |
|
}) |
|
}).then(function (response) { |
|
if (!response) { |
|
return |
|
} |
|
var cacheDate = response['$cacheDate'] |
|
if (cacheDate) { |
|
cacheDate = new Date(cacheDate) |
|
} |
|
var success = !cacheDate |
|
trainData = response |
|
onTrainData(response, cacheDate) |
|
// Check in 1 seconds if network error |
|
reschedule(success ? undefined : 1000) |
|
return success |
|
}).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() { |
|
function changeRskText(newText) { |
|
document.querySelectorAll('.rsk').forEach(function (elem) { |
|
elem.textContent = newText |
|
}) |
|
} |
|
|
|
changeRskText('Refreshing...') |
|
refresh().catch(function () { return false}).then(function (success) { |
|
if (!success) { |
|
changeRskText('Refreshing failed') |
|
setTimeout(function (_) { |
|
changeRskText('Refresh') |
|
}, 3000) |
|
} |
|
else { |
|
changeRskText('Refresh') |
|
} |
|
}) |
|
} |
|
|
|
window.addEventListener('popstate', function (e) { |
|
groupIndex = null |
|
if (trainData) { |
|
onTrainData(trainData) |
|
} |
|
else { |
|
refresh() |
|
} |
|
}) |
|
|
|
window.addEventListener('load', function (e) { |
|
if (!new URL(window.location.href).searchParams.has('train')) { |
|
window.history.back() |
|
this.setTimeout(function () { |
|
var url = new URL(window.location.href) |
|
url.pathname = 'train.html' |
|
window.location.href = url.toString() |
|
}, 100) |
|
} |
|
|
|
var sp = new URL(window.location.href).searchParams |
|
|
|
trainNumber = sp.get('train') |
|
date = sp.has('date') ? new Date(sp.get('date')) : new Date() |
|
groupIndex = sp.has('groupIndex') ? parseInt(sp.get('groupIndex')) : null |
|
|
|
if (window.localStorage) { |
|
var oldRecent = localStorage.getItem('recent/view-train') |
|
if (oldRecent) { |
|
oldRecent = JSON.parse(oldRecent) |
|
} |
|
localStorage.setItem('recent/view-train', JSON.stringify({ |
|
trainNumber: trainNumber, |
|
date: date.toISOString(), |
|
groupIndex: oldRecent && oldRecent.trainNumber === trainNumber ? oldRecent.groupIndex : undefined, |
|
$addDate: new Date().toISOString(), |
|
})) |
|
} |
|
|
|
document.querySelectorAll('.rsk').forEach(function (rskElem) { |
|
rskElem.addEventListener('click', function (e) { |
|
rsk() |
|
}) |
|
}) |
|
|
|
if (navigator.canShare && navigator.canShare({ url: '' })) { |
|
document.getElementById('title').addEventListener('click', function () { |
|
navigator.share({ url: '' }); |
|
}) |
|
} |
|
|
|
var content = document.getElementsByClassName('content')[0] |
|
content.focus() |
|
content.addEventListener('keydown', function (e) { |
|
switch (e.key) { |
|
// case 'ArrowUp': |
|
// content.scrollBy(0, -50) |
|
// break |
|
// case 'ArrowDown': |
|
// content.scrollBy(0, 50) |
|
// break |
|
case 'SoftRight': |
|
rsk() |
|
break |
|
case '1': |
|
date.setDate(date.getDate() - 1) |
|
refresh() |
|
break |
|
case '3': |
|
date.setDate(date.getDate() + 1) |
|
refresh() |
|
break |
|
case '7': |
|
showKm = !showKm |
|
document.querySelectorAll('.km').forEach(function (kmItem) { |
|
if (showKm) { |
|
kmItem.classList.remove('hidden') |
|
} |
|
else { |
|
kmItem.classList.add('hidden') |
|
} |
|
}) |
|
break |
|
default: |
|
console.log(e.key) |
|
} |
|
}) |
|
|
|
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) |
|
|
|
if (this.localStorage && !this.localStorage.getItem('info-yesterday')) { |
|
this.alert("New feature: You can now view yesterday's train by selecting the date!") |
|
this.localStorage.setItem('info-yesterday', 'true') |
|
} |
|
})
|
|
|