commit f5c01f97c9ef4c6e021ba37960e7ad9a2fb91cb0 Author: Dan Cojocaru Date: Wed Jul 13 00:33:59 2022 +0300 Initial commit diff --git a/back.js b/back.js new file mode 100644 index 0000000..0c8361b --- /dev/null +++ b/back.js @@ -0,0 +1,8 @@ +window.addEventListener('load', function (e) { + this.document.body.addEventListener('keydown', function (e) { + if (e.key == 'Backspace') { + e.preventDefault() + window.history.back() + } + }) +}) diff --git a/base.css b/base.css new file mode 100644 index 0000000..7308c21 --- /dev/null +++ b/base.css @@ -0,0 +1,199 @@ +html { + height: 100vh; + padding: 0; + margin: 0; +} + +body { + height: 100vh; + padding: 0; + margin: 0; + + font-size: 17px; + font-weight: 400; + + display: flex; + flex-direction: column; + align-items: stretch; +} + +.content { + height: 100%; + + overflow-y: scroll; + + -ms-overflow-style: none; + scrollbar-width: none; +} + +.content::-webkit-scrollbar { + display: none; +} + +footer { + margin-top: auto; + display: flex; + + background-color: #e0e0e0; +} + +footer * { + text-transform: capitalize; +} + +footer .lsk { + text-align: start; +} + +footer .csk { + flex-grow: 1; + text-align: center; + text-transform: uppercase; +} + +footer .rsk { + text-align: end; +} + +h1 { + font-size: 17px; + font-weight: 400; + text-align: center; + + margin: 8px; +} + +h2 { + font-size: 17px; + font-weight: 600; + + margin: 1px 0; + padding-left: 8px; + padding-right: 8px; +} + +h3 { + font-size: 14px; + font-weight: 400; + + margin: 1px 0; + padding-left: 8px; + padding-right: 8px; +} + +h4 { + font-size: 14px; + font-weight: 400; + + margin: 1px 0; + padding-left: 8px; + padding-right: 8px; + + color: #606060; + background-color: #f0f0f0; +} + +h5 { + font-size: 14px; + font-weight: 600; +} + +p.pri { + font-size: 17px; + font-weight: 400; + + margin: 0 8px; +} + +p.sec { + font-size: 14px; + font-weight: 400; + + margin: 0 8px; + + color: gray; +} + +p.thi { + font-size: 12px; + font-weight: 400; + + margin: 0 8px; + + color: gray; +} + +p, ul { + font-size: 17px; + font-weight: 400; +} + +p.link, a { + font-size: 17px; + font-weight: 700; +} + +p.btn, button { + font-size: 17px; + font-weight: 400; + + margin: 8px; + padding: 8px; + border: 1px solid grey; + border-radius: 10px; +} + +ul { + padding: 0; + margin: 0; +} + +li { + list-style: none; + margin: 0; + + display: block; +} + +li:focus { + color: white; + background-color: blue; +} + +a { + display: block; + + padding: 8px; + + color: black; + text-decoration: none; +} + +a.disabled { + color: grey; +} + +a:not(.disabled):hover:not(:focus) { + color: black; + background-color: lightskyblue; +} + +a:focus { + color: white; + background-color: blue; +} + +input { + display: block; + + box-sizing: border-box; + width: calc(100% - 16px); + + margin: 8px; + padding: 2px; + border: 2px solid grey; +} + +.hidden { + display: none; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..ca003cf --- /dev/null +++ b/index.html @@ -0,0 +1,28 @@ + + + + + + + InfoTren + + + + + + +

InfoTren

+ +
+ +
+ + + + \ No newline at end of file diff --git a/items.js b/items.js new file mode 100644 index 0000000..3125c78 --- /dev/null +++ b/items.js @@ -0,0 +1,48 @@ +var currentIndex = 0 + +function nav(offset) { + var items = document.querySelectorAll('.items:not(.disabled)') + if (offset === -1) { + if (currentIndex <= 0) { + return + } + } + else if (offset === 1) { + if (currentIndex >= items.length - 1) { + return + } + } + else { + console.error(`nav called with unknown offset: ${offset}`) + } + + currentIndex += offset + items[currentIndex].focus() + items[currentIndex].addEventListener('keydown', handleKeyDown) +} + +function handleKeyDown(e) { + switch (e.key) { + case 'ArrowUp': + e.preventDefault() + e.stopPropagation() + nav(-1) + break + case 'ArrowDown': + e.preventDefault() + e.stopPropagation() + nav(1) + break + } +} + +window.addEventListener('load', function (e) { + // Select first item + var items = document.querySelectorAll('.items:not(.disabled)') + if (items.length > 0) { + items[0].focus() + items[0].addEventListener('keydown', handleKeyDown) + } + + document.body.addEventListener('keydown', handleKeyDown) +}) diff --git a/manifest.webapp b/manifest.webapp new file mode 100644 index 0000000..a954a30 --- /dev/null +++ b/manifest.webapp @@ -0,0 +1,16 @@ +{ + "version": "1", + "name": "InfoTren", + "launch_path": "/index.html", + "description": "Frontend for InfoFer scraper", + "developer": { + "name": "Dan Cojocaru", + "url": "https://dcdev.ro" + }, + "installs_allowed_from": [ + "*" + ], + "default_locale": "en", + "permissions": {}, + "cursor": false +} \ No newline at end of file diff --git a/train.html b/train.html new file mode 100644 index 0000000..55630d3 --- /dev/null +++ b/train.html @@ -0,0 +1,30 @@ + + + + + + + Train - InfoTren + + + + + + + + +

Train Information

+ +

Train Number

+ + +

Suggestions

+
+ +
+ + + + \ No newline at end of file diff --git a/train.js b/train.js new file mode 100644 index 0000000..79b0cde --- /dev/null +++ b/train.js @@ -0,0 +1,109 @@ +var knownTrains = [] + +function goToTrain(number) { + var url = new URL(window.location.href) + url.pathname = 'view-train.html' + url.searchParams.set('train', number) + url.searchParams.set('date', new Date().toISOString()) + window.location.href = url.toString() +} + +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 trainNumberInput = document.getElementById('trainNumber') + var trainNumber = trainNumberInput.value.trim() + + var suggestions = [] + for (var i = 0; i < knownTrains.length; i++) { + if (trainNumber) { + if (!knownTrains[i].number.includes(trainNumber)) { + continue + } + } + suggestions.push(knownTrains[i]) + } + + suggestions.forEach(function (suggestion, index) { + 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) { + goToTrain(suggestion.number) + } + suggestionLi.addEventListener('click', onAction) + suggestionLi.addEventListener('keypress', function (e) { + if (e.key == 'Enter') { + onAction(e) + } + }) + + var trainNameP = document.createElement('p') + suggestionLi.appendChild(trainNameP) + + trainNameP.textContent = `${suggestion.rank} ${suggestion.number}` + trainNameP.classList.add('pri', 'trainName') + + var trainCompanyP = document.createElement('p') + suggestionLi.appendChild(trainCompanyP) + + trainCompanyP.textContent = suggestion.company + trainCompanyP.classList.add('thi') + }, 0) + }) + + setTimeout(function () { + _rebuildDebounce = null + if (_rebuildRequested) { + rebuildSuggestions() + } + }, 500) +} + +window.addEventListener('load', function (e) { + var trainNumber = document.getElementById('trainNumber') + trainNumber.addEventListener('input', function (e) { + rebuildSuggestions() + }) + trainNumber.addEventListener('focus', function (e) { + document.getElementsByClassName('csk')[0].textContent = 'Search' + }) + trainNumber.addEventListener('blur', function (e) { + document.getElementsByClassName('csk')[0].textContent = 'Select' + }) + trainNumber.addEventListener('keypress', function (e) { + if (e.key == 'Enter') { + goToTrain(trainNumber.value.trim()) + } + }) + + fetch('https://scraper.infotren.dcdev.ro/v2/trains') + .then(function (response) { + return response.json() + }) + .then(function (response) { + knownTrains = response + knownTrains.sort(function(a, b) { return a.number - b.number }) + }) + .then(function () { + rebuildSuggestions() + }) +}) diff --git a/view-train.css b/view-train.css new file mode 100644 index 0000000..7d603f5 --- /dev/null +++ b/view-train.css @@ -0,0 +1,75 @@ +.IR, .IRN { + color: red; +} + +.early { + color: green; +} + +.late { + color: red; +} + +.station { + color: black; +} + +#company { + text-align: center; +} + +.stationItem { + display: grid; + grid-template-columns: 50px auto 50px; + grid-template-rows: auto; + grid-template-areas: + "arr name dep" + "arr km dep" + "arr platform dep"; + padding: 4px 0; +} + +.stationItem .name { + text-align: center; + grid-area: name; +} + +.stationItem .arrival { + text-align: start; + grid-area: arr; + + align-items: flex-start; +} + +.stationItem .departure { + text-align: end; + grid-area: dep; + + align-items: flex-end; +} + +.stationItem .arrival, .station .departure { + align-self: center; + + display: flex; + flex-direction: column; +} + +.stationItem .arrival .original, .stationItem .departure .original { + color: #a0a0a0; + text-decoration: line-through; +} + +.stationItem .arrival .not-real, .stationItem .departure .not-real { + font-style: italic; +} + +.stationItem .km { + text-align: center; + grid-area: km; +} + +.stationItem .platform { + text-align: center; + grid-area: platform; +} diff --git a/view-train.html b/view-train.html new file mode 100644 index 0000000..d910528 --- /dev/null +++ b/view-train.html @@ -0,0 +1,42 @@ + + + + + + + View Train - InfoTren + + + + + + + + +

Train Info

+

+ +
+
+

Route

+

From

+

+

To

+

+
+ +
+

Stations

+
+
+ + + + \ No newline at end of file diff --git a/view-train.js b/view-train.js new file mode 100644 index 0000000..551275e --- /dev/null +++ b/view-train.js @@ -0,0 +1,258 @@ +var trainNumber +var date + +var showKm = false + +var trainData = null + +function onTrainData(data) { + var title = document.getElementsByTagName('h1')[0] + title.textContent = '' + title.appendChild(document.createTextNode('Train ')) + var rankSpan = document.createElement('span') + rankSpan.textContent = data.rank + rankSpan.classList.add(data.rank) + title.appendChild(rankSpan) + title.appendChild(document.createTextNode(` ${data.number}`)) + + document.getElementById('company').textContent = data.operator + + document.getElementById('route-from').textContent = data.route.from + document.getElementById('route-to').textContent = data.route.to + + if (data.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 = data.status.delay + if (delayMinutes === 0) { + delayString = 'No delay' + 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 (data.status.state === 'arrival') { + stateString += 'when arriving at ' + } + else if (data.status.state === 'departure') { + stateString += 'when departing from ' + } + else if (data.status.state === 'passing') { + stateString += 'while passing through ' + } + statusLocation.appendChild(document.createTextNode(stateString)) + var stationSpan = document.createElement('span') + statusLocation.appendChild(stationSpan) + stationSpan.textContent = data.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) + + data.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.textContent = station.name + stationName.classList.add('pri', 'name') + + if (station.arrival) { + var stationArrival = document.createElement('div') + stationItem.appendChild(stationArrival) + stationArrival.classList.add('arrival') + + var originalArr = document.createElement('p') + stationArrival.appendChild(originalArr) + var arrDate = new Date(station.arrival.scheduleTime) + originalArr.textContent = `${arrDate.getHours().toString().padStart(2, "0")}:${arrDate.getMinutes().toString().padStart(2, "0")}` + originalArr.classList.add('pri') + if (station.arrival.status && station.arrival.status.delay != 0) { + originalArr.classList.remove('pri') + originalArr.classList.add('thi', 'original') + + var actualArr = document.createElement('p') + stationArrival.appendChild(actualArr) + arrDate.setMinutes(arrDate.getMinutes() + station.arrival.status.delay) + actualArr.textContent = `${arrDate.getHours().toString().padStart(2, "0")}:${arrDate.getMinutes().toString().padStart(2, "0")}` + actualArr.classList.add('pri', station.arrival.status.delay > 0 ? 'late' : 'early') + if (!station.arrival.status.real) { + actualArr.classList.add('not-real') + } + } + } + + 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) + originalDep.textContent = `${depDate.getHours().toString().padStart(2, "0")}:${depDate.getMinutes().toString().padStart(2, "0")}` + originalDep.classList.add('pri') + if (station.departure.status && station.departure.status.delay != 0) { + originalDep.classList.remove('pri') + originalDep.classList.add('thi', 'original') + + var actualDep = document.createElement('p') + stationDeparture.appendChild(actualDep) + depDate.setMinutes(depDate.getMinutes() + station.departure.status.delay) + actualDep.textContent = `${depDate.getHours().toString().padStart(2, "0")}:${depDate.getMinutes().toString().padStart(2, "0")}` + actualDep.classList.add('pri', station.departure.status.delay > 0 ? 'late' : 'early') + if (!station.departure.status.real) { + actualDep.classList.add('not-real') + } + } + } + + 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') + } + }) +} + +var refreshStopToken = null +function refresh() { + fetch(`https://scraper.infotren.dcdev.ro/v2/train/${trainNumber}?date=${date.toISOString()}`) + .then(function (response) { + return response.json() + }) + .then(function (response) { + trainData = response + onTrainData(response) + }) + .then(function () { + refreshStopToken = setTimeout(function () { + refresh() + }, 60000) + }) +} + +window.addEventListener('unload', function (e) { + if (refreshStopToken != null) { + clearTimeout(refreshStopToken) + } +}) + +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() + + 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': + refresh() + break + case '*': + 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() +})