Acknowledgements
diff --git a/base.css b/base.css
index 67286f3..50275ca 100644
--- a/base.css
+++ b/base.css
@@ -73,6 +73,13 @@ footer .rsk {
}
header {
+ background-color: white;
+
+ display: flex;
+ align-items: center;
+}
+
+header.embedded {
position: fixed;
left: env(titlebar-area-x, 0);
top: env(titlebar-area-y, 0);
@@ -81,19 +88,6 @@ header {
-webkit-app-region: drag;
app-region: drag;
- background-color: white;
-
- display: flex;
- align-items: center;
-}
-
-.header-placeholder {
- box-sizing: border-box;
- height: max(env(titlebar-area-height, 36px), 42px);
- margin-bottom: 2px;
-}
-
-header.embedded {
background-color: #0000ff;
color: white;
}
@@ -108,6 +102,14 @@ header.embedded {
@media (display-mode: window-controls-overlay) {
header {
+ position: fixed;
+ left: env(titlebar-area-x, 0);
+ top: env(titlebar-area-y, 0);
+ width: env(titlebar-area-width, 100%);
+ min-height: env(titlebar-area-height, 36px);
+ -webkit-app-region: drag;
+ app-region: drag;
+
background-color: #0000ff;
color: white;
}
@@ -128,8 +130,13 @@ header.embedded {
}
.header-placeholder {
+ box-sizing: border-box;
+ height: max(env(titlebar-area-height, 36px), 42px);
+ margin-bottom: 2px;
+
background-color: #0000ff;
}
+
}
header .left, header .right {
@@ -206,7 +213,7 @@ p.sec {
margin: 0 8px;
- color: gray;
+ color: grey;
}
p.thi {
@@ -344,14 +351,26 @@ pre {
border-bottom-right-radius: 5%;
}
-.IR, .IRN {
- color: #ff0000 !important;
+.product-suburban {
+ color: green !important;
+}
+
+.product-national, .product-nationalExpress {
+ font-style: italic;
+ font-weight: 500;
}
-.IC {
- color: #00aa00 !important;
+.product-bus {
+ color: purple !important;
}
+.product-subway {
+ color: blue !important;
+}
+
+.product-tram {
+ color: lightcoral !important;
+}
@media print {
footer, .no-print {
diff --git a/base.dark.css b/base.dark.css
new file mode 100644
index 0000000..d9aab4e
--- /dev/null
+++ b/base.dark.css
@@ -0,0 +1,49 @@
+@media (prefers-color-scheme: dark) {
+
+ body {
+ color: white;
+ background-color: black;
+ }
+
+ header:not(.embedded) {
+ background-color: black;
+ }
+
+ footer {
+ background-color: #303030;
+ }
+
+ h4 {
+ color: #a0a0a0;
+ background-color: #0f0f0f;
+ }
+
+ p.sec, p.thi {
+ color: lightgrey;
+ }
+
+ li.items:not(.disabled):hover:not(:focus), a:not(.disabled):hover:not(:focus) {
+ background-color: #0000bb;
+ color: white;
+ }
+
+ a:not(.no-a-custom):not(.no-custom-a):not(:focus):not(:hover) {
+ color: white;
+ }
+
+ .back {
+ filter: invert(1);
+ }
+
+ .product-suburban {
+ color: #33ff33 !important;
+ }
+
+ .product-bus {
+ color: #dd33dd !important;
+ }
+
+ .product-subway {
+ color: #33aaff !important;
+ }
+}
diff --git a/common/components.js b/common/components.js
new file mode 100644
index 0000000..5ba3360
--- /dev/null
+++ b/common/components.js
@@ -0,0 +1,88 @@
+// Adapted from: https://github.com/tsoding/grecha.js/blob/master/grecha.js
+
+function tag(name) {
+ var result = document.createElement(name);
+ for (var i = 1; i < arguments.length; i++) {
+ var child = arguments[i];
+ if (child instanceof Node) {
+ result.appendChild(child)
+ } else {
+ result.appendChild(document.createTextNode(child ? child.toString() : ''))
+ }
+ }
+
+ result.att$ = function(name, value) {
+ this.setAttribute(name, value);
+ return this;
+ };
+
+ result.value$ = function(value) {
+ this.value = value;
+ return this;
+ }
+
+ result.checked$ = function(value) {
+ this.checked = value;
+ return this;
+ }
+
+ result.id$ = function(name) {
+ this.id = name;
+ return this;
+ };
+
+ result.class$ = function(name) {
+ this.classList.add(name);
+ return this;
+ };
+
+ result.event$ = function(eventName, handler) {
+ this.addEventListener(eventName, handler);
+ return this;
+ };
+
+ result.onclick$ = function(callback) {
+ this.onclick = callback;
+ return this;
+ };
+
+ result.also$ = function(callback) {
+ callback(this);
+ return this;
+ };
+
+ result.let$ = function(callback) {
+ return callback(this);
+ };
+
+ return result;
+}
+
+var MUNDANE_TAGS = ["canvas", "h1", "h2", "h3", "h4", "p", "a", "div", "span", "select", "label", "hr"];
+for (var i = 0; i < MUNDANE_TAGS.length; i++) {
+ (function (tagName) {
+ window[tagName] = function() {
+ var args = [tagName];
+ for (var j = 0; j < arguments.length; j++) {
+ args.push(arguments[j]);
+ }
+ return tag.apply(null, args);
+ }
+ })(MUNDANE_TAGS[i]);
+}
+
+function a(href) {
+ var args = ["a"];
+ for (var i = 1; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ return tag.apply(null, args).att$("href", href);
+}
+
+function img(src) {
+ return tag("img").att$("src", src);
+}
+
+function input(type) {
+ return tag("input").att$("type", type);
+}
diff --git a/config-route.css b/config-route.css
new file mode 100644
index 0000000..b12e9eb
--- /dev/null
+++ b/config-route.css
@@ -0,0 +1,146 @@
+.itinerary-train {
+ display: grid;
+ grid-template-columns: auto 1fr auto;
+ grid-template-rows: repeat(auto-fit, auto);
+ align-items: center;
+}
+
+.itinerary-train:not(:last-child) {
+ grid-template-areas:
+ "dep-time dep-station dep-platform"
+ "train train train"
+ "arr-time arrdep-station arr-platform"
+ "dep2-time arrdep-station dep2-platform";
+}
+
+.itinerary-train:last-child {
+ grid-template-areas:
+ "train train train"
+ "arr-time arr-station arr-platform";
+}
+
+.itinerary-train:only-child {
+ grid-template-areas:
+ "dep-time dep-station dep-platform"
+ "train train train"
+ "arr-time arr-station arr-platform";
+}
+
+.itinerary-train .departure.time {
+ grid-area: dep-time;
+}
+
+.itinerary-train .departure.platform {
+ grid-area: dep-platform;
+}
+
+.itinerary-train .next-departure.time {
+ grid-area: dep2-time;
+}
+
+.itinerary-train .next-departure.platform {
+ grid-area: dep2-platform;
+}
+
+.itinerary-train .departure.station {
+ grid-area: dep-station;
+}
+
+.itinerary-train .train {
+ grid-area: train;
+}
+
+.itinerary-train .arrival.time {
+ grid-area: arr-time;
+}
+
+.itinerary-train .arrival.platform {
+ grid-area: arr-platform;
+}
+
+.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;
+ align-self: center;
+}
+
+.itinerary-train .platform {
+ margin: 2px;
+ padding: 2px;
+ border: 1px solid black;
+ border-radius: 4px;
+ justify-self: end;
+ min-width: 16px;
+ text-align: center;
+}
+
+.itinerary-train .platform.changed {
+ color: red;
+ border-color: red;
+}
+
+.train .company {
+ font-size: 0.8em;
+ font-style: italic;
+}
+
+.walking {
+ font-size: 0.95em;
+ font-style: italic;
+}
+
+input#time {
+ margin: 1px;
+}
+
+div.checkbox {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+div.checkbox p {
+ flex: 1;
+}
+
+div.checkbox input {
+ flex: 0;
+}
+
+.suggestion {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.suggestion :first-child {
+ flex: 1;
+}
+
+.suggestion .star {
+ flex: 0;
+ pointer-events: none;
+}
+
+.suggestion .star.checked {
+ filter: invert(90%) sepia(49%) saturate(704%) hue-rotate(359deg) brightness(94%) contrast(99%);
+}
+
+@media (prefers-color-scheme: dark) {
+ .suggestion .star {
+ filter: invert(100%);
+ }
+
+ .suggestion .star.checked {
+ filter: invert(86%) sepia(79%) saturate(2126%) hue-rotate(357deg) brightness(108%) contrast(104%);
+ }
+}
diff --git a/config-route.html b/config-route.html
new file mode 100644
index 0000000..a3fdfe1
--- /dev/null
+++ b/config-route.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
Route - InfoDTrain
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Find Route
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config-route.js b/config-route.js
new file mode 100644
index 0000000..3b7c12a
--- /dev/null
+++ b/config-route.js
@@ -0,0 +1,674 @@
+/**
+ * @type {string | null}
+ */
+var fromStation = null
+/**
+ * @type {string | null}
+ */
+var toStation = null
+/**
+ * @type {Date | null}
+ */
+var departureDate = null
+var transitKind = {
+ ice: true,
+ ic: true,
+ re: true,
+ rb: true,
+ s: true,
+ bus: true,
+ ferry: true,
+ u: true,
+ tram: true,
+}
+var starred = []
+
+/**
+ * @type {{id: string, name: string}[]}
+ */
+var knownStations = []
+
+var itineraries = null
+
+function goToStation(stationId) {
+ var url = new URL(window.location.href)
+ if (!fromStation) {
+ url.searchParams.set('from', stationId)
+ }
+ else if (!toStation) {
+ url.searchParams.set('to', stationId)
+ }
+ // 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 suggestions = knownStations.slice()
+ if (suggestions.length === 0) {
+ suggestions = starred.map(function (s) { return JSON.parse(s) })
+ }
+
+ suggestions.forEach(function (suggestion, index) {
+ var suggestionDiv = document.createElement('div')
+ suggestionsArea.appendChild(suggestionDiv)
+ suggestionDiv.classList.add('suggestion')
+
+ var suggestionLi = document.createElement('li')
+ suggestionDiv.appendChild(suggestionLi)
+
+ setTimeout(function () {
+ suggestionLi.classList.add('items')
+ suggestionLi.tabIndex = index + 1
+ suggestionLi.style.padding = '2px 0'
+
+ function onAction(e) {
+ goToStation(JSON.stringify(suggestion))
+ }
+ 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 || suggestion.address
+ stationNameP.classList.add('pri', 'stationName')
+
+ if (window.localStorage) {
+ var suggestionLink = document.createElement('a')
+ suggestionDiv.appendChild(suggestionLink)
+ suggestionLink.classList.add('no-custom-a')
+
+ var suggestionStar = document.createElement('object')
+ suggestionLink.appendChild(suggestionStar)
+ suggestionStar.classList.add('star')
+ suggestionStar.type = 'image/svg+xml'
+ function setStar() {
+ if (starred.includes(JSON.stringify(suggestion))) {
+ suggestionStar.data = '/icons/star_full.svg'
+ suggestionStar.classList.add('checked')
+ }
+ else {
+ suggestionStar.data = '/icons/star_empty.svg'
+ suggestionStar.classList.remove('checked')
+ }
+ }
+ suggestionLink.addEventListener('click', function (event) {
+ event.preventDefault()
+ if (starred.includes(JSON.stringify(suggestion))) {
+ starred = starred.filter(function (s) {
+ return s !== suggestion
+ })
+ } else {
+ starred.push(JSON.stringify(suggestion))
+ }
+ setStar()
+ localStorage.setItem('stations/starred', JSON.stringify(starred))
+ })
+ setStar()
+ }
+
+ // 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)
+}
+
+var fetchAbortController = new AbortController()
+function reloadSuggestions() {
+ var stationNameInput = document.getElementById('stationName')
+ var stationName = searchNormalize(stationNameInput.value.trim())
+
+ var locationsUrl = new URL('https://v6.db.transport.rest/locations')
+ locationsUrl.searchParams.set('query', stationName)
+ locationsUrl.searchParams.set('limit', '25')
+ locationsUrl.searchParams.set('fuzzy', 'true')
+ locationsUrl.searchParams.set('stops', 'true')
+ locationsUrl.searchParams.set('addresses', 'true')
+ locationsUrl.searchParams.set('poi', 'true')
+
+ fetchAbortController.abort()
+ fetchAbortController = new AbortController()
+ fetch(locationsUrl.toString(), { signal: fetchAbortController.signal })
+ .then(function (response) {
+ if (response.ok) {
+ return response.json()
+ }
+ else {
+ return {}
+ }
+ })
+ .then(function (data) {
+ if (data) {
+ knownStations = Object.values(data)
+ rebuildSuggestions()
+ }
+ })
+}
+
+/**
+ * @typedef DbJourney
+ * @prop {'journey'} type
+ * @prop {(DbTrip & DbArrDep & {tripId: string})[]} legs
+ * @prop {string} refreshToken
+ * @prop {DbRemark[]} remarks
+ */
+
+/**
+ * @param {{journeys: DbJourney[]}} 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.journeys.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.journeys[i].legs.forEach(function (train, idx) {
+ var last = idx === data.journeys[i].legs.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.plannedDeparture)
+ departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
+
+ var departureHeading = document.createElement('h3')
+ trainDiv.appendChild(departureHeading)
+ departureHeading.classList.add('departure', 'station')
+ if (train.origin.type === 'stop' || train.origin.type === 'station') {
+ var departureLink = document.createElement('a')
+ departureHeading.appendChild(departureLink)
+ departureLink.textContent = train.origin.name
+ departureLink.classList.add('no-custom-a', 'items')
+ var departureUrl = new URL('/view-station.html', window.location.origin)
+ departureUrl.searchParams.set('stationId', train.origin.id)
+ departureLink.href = departureUrl.toString()
+ }
+ else {
+ var departureSpan = document.createElement('span')
+ departureHeading.append(departureSpan)
+ departureSpan.innerText = train.origin.name || train.origin.address
+ }
+
+ if (train.departurePlatform || train.plannedDeparturePlatform) {
+ var departurePlatformP = document.createElement('p')
+ trainDiv.append(departurePlatformP)
+ departurePlatformP.classList.add('sec', 'departure', 'platform')
+ if (train.departurePlatform && train.departurePlatform != train.plannedDeparturePlatform) {
+ departurePlatformP.classList.add('changed')
+ }
+ departurePlatformP.textContent = `${train.departurePlatform || train.plannedDeparturePlatform}`
+ }
+ }
+
+ var trainP = document.createElement('p')
+ trainDiv.appendChild(trainP)
+ trainP.classList.add('pri', 'train')
+ if (!train.walking) {
+ var trainLink = document.createElement('a')
+ trainP.appendChild(trainLink)
+ trainLink.innerText = train.line.name
+ trainLink.classList.add('no-custom-a', 'items')
+ var trainUrl = new URL('/view-train.html', window.location.origin)
+ trainUrl.searchParams.set('tripId', train.tripId)
+ trainLink.href = trainUrl.toString()
+ trainP.appendChild(document.createTextNode(' '))
+ if (train.line.operator) {
+ var trainCompany = document.createElement('span')
+ trainP.appendChild(trainCompany)
+ trainCompany.textContent = '(' + train.line.operator.name + ')'
+ trainCompany.classList.add('company')
+ }
+ }
+ else {
+ var walkingSpan = document.createElement('span')
+ trainP.append(walkingSpan)
+ walkingSpan.classList.add('walking')
+ walkingSpan.innerText = `Walking (${train.distance} m)`
+ }
+
+ 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.plannedArrival)
+ arrivalTimePre.textContent = arrival.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
+
+ var arrivalHeading = document.createElement('h3')
+ trainDiv.appendChild(arrivalHeading)
+ arrivalHeading.classList.add('arrival', 'station')
+ if (train.destination.type === 'stop' || train.destination.type === 'station') {
+ var arrivalLink = document.createElement('a')
+ arrivalHeading.appendChild(arrivalLink)
+ arrivalLink.textContent = train.destination.name
+ arrivalLink.classList.add('no-custom-a', 'items')
+ var arrivalUrl = new URL('/view-station.html', window.location.origin)
+ arrivalUrl.searchParams.set('stationId', train.destination.id)
+ arrivalLink.href = arrivalUrl.toString()
+ }
+ else {
+ var arrivalSpan = document.createElement('span')
+ arrivalHeading.append(arrivalSpan)
+ arrivalSpan.innerText = train.destination.name || train.origin.address
+ }
+
+ if (train.arrivalPlatform || train.plannedArrivalPlatform) {
+ var arrivalPlatformP = document.createElement('p')
+ trainDiv.append(arrivalPlatformP)
+ arrivalPlatformP.classList.add('sec', 'arrival', 'platform')
+ if (train.arrivalPlatform && train.arrivalPlatform != train.plannedArrivalPlatform) {
+ arrivalPlatformP.classList.add('changed')
+ }
+ arrivalPlatformP.textContent = `${train.arrivalPlatform || train.plannedArrivalPlatform}`
+ }
+
+ if (!last) {
+ var nextTrain = data.journeys[i].legs[idx + 1]
+ 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(nextTrain.plannedDeparture)
+ departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
+
+ if (nextTrain.departurePlatform || nextTrain.plannedDeparturePlatform) {
+ var departurePlatformP = document.createElement('p')
+ trainDiv.append(departurePlatformP)
+ departurePlatformP.classList.add('sec', 'next-departure', 'platform')
+ if (nextTrain.departurePlatform && nextTrain.departurePlatform != nextTrain.plannedDeparturePlatform) {
+ departurePlatformP.classList.add('changed')
+ }
+ departurePlatformP.textContent = `${nextTrain.departurePlatform || nextTrain.plannedDeparturePlatform}`
+ }
+ }
+ })
+ }
+}
+
+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')
+ var fromJson = JSON.parse(fromStation || 'null')
+ toStation = sp.get('to')
+ var toJson = JSON.parse(toStation || 'null')
+ 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 = `${fromJson.name || fromJson.address} - ${toJson.name || toJson.address}`
+ titleH1.textContent = 'Find Route - Configure'
+ }
+
+ if (window.localStorage) {
+ var maybeTransitKind = JSON.parse(localStorage.getItem('config-route/transitKind'))
+ if (maybeTransitKind) {
+ transitKind = maybeTransitKind
+ }
+
+ var maybeStarred = JSON.parse(localStorage.getItem('stations/starred'))
+ if (maybeStarred) {
+ starred = maybeStarred
+ }
+ }
+
+ var footer = document.querySelector('footer')
+
+ if (!fromStation || !toStation) {
+ // Build station selection UI
+ document.body.insertBefore(
+ h4(
+ label('Station Name').att$('for', 'stationName'),
+ ),
+ footer,
+ )
+ // 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'
+
+ document.body.insertBefore(
+ input('search').id$('stationName').att$('name', 'stationName').class$('items'),
+ footer,
+ )
+ // var stationNameInput = document.createElement('input')
+ // document.body.insertBefore(stationNameInput, footer)
+ // stationNameInput.type = 'search'
+ // stationNameInput.classList.add('items')
+ // stationNameInput.name = 'stationName'
+ // stationNameInput.id = 'stationName'
+
+ document.body.insertBefore(h4('Suggestions'), footer)
+ // 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'
+ rebuildSuggestions()
+
+ document.querySelector('.csk').textContent = 'Search'
+
+ var stationName = document.getElementById('stationName')
+ stationName.addEventListener('input', function (e) {
+ reloadSuggestions()
+ })
+ 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())
+ }
+ })
+ }
+ 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 = -1, departureOption = (function () { var d = new Date(); d.setDate(d.getDate() - 1); return d })(); 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
+ })
+ if (i === 0) {
+ suggestionLi.focus()
+ }
+ })()
+ 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')}`
+ if (i === 0) {
+ innerPre.textContent += ' (today)'
+ }
+ }
+
+ 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'
+
+ function updateSearchLink() {
+ var a = document.getElementById('search-link')
+ var url = new URL(window.location.href)
+ url.pathname = 'route.html'
+ url.searchParams.set('departureDate', departureDate.toISOString())
+ url.searchParams.set('from', JSON.stringify(fromJson))
+ url.searchParams.set('to', JSON.stringify(toJson))
+ url.searchParams.set('transitKind', JSON.stringify(transitKind))
+ a.href = url.toString()
+ }
+
+ function transitKindCheckChanged(event) {
+ transitKind[event.target.id.slice(13)] = event.target.checked
+ if (window.localStorage) {
+ localStorage.setItem('config-route/transitKind', JSON.stringify(transitKind))
+ }
+ updateSearchLink()
+ }
+
+ var contentDiv = div(
+ h4('Route'),
+ p('From').class$('thi'),
+ p(fromJson.name || fromJson.address).class$('pri'),
+ p('To').class$('thi'),
+ p(toJson.name || toJson.address).class$('pri'),
+ // a('', 'Configure via...'),
+ h4('Date and time'),
+ p('Date').class$('thi'),
+ p(departureDate.toDateString()).class$('pri'),
+ p(label('Time').att$('for', 'time')).class$('thi'),
+ p(
+ input('time')
+ .id$('time')
+ .att$('value', departureDate.getHours().toString().padStart(2, '0') + ':' + departureDate.getMinutes().toString().padStart(2, '0'))
+ .event$('input', function(event) {
+ var text = event.target.value
+ var splitted = text.toString().split(':')
+ var h = parseInt(splitted[0])
+ var m = parseInt(splitted[1])
+ departureDate.setHours(h, m)
+ updateSearchLink()
+ }),
+ ),
+ h4('Train categories'),
+ div(
+ p(label('ICE, RJX, High speed').class$('product-nationalExpress').att$('for', 'transit-kind-ice')),
+ input('checkbox')
+ .checked$(transitKind.ice)
+ .id$('transit-kind-ice')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('IC, EC, RJ').class$('product-national').att$('for', 'transit-kind-ic')),
+ input('checkbox')
+ .checked$(transitKind.ic)
+ .id$('transit-kind-ic')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('RE, IRE').class$('product-regionalExpress').att$('for', 'transit-kind-re')),
+ input('checkbox')
+ .checked$(transitKind.re)
+ .id$('transit-kind-re')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('RB').class$('product-regional').att$('for', 'transit-kind-rb')),
+ input('checkbox')
+ .checked$(transitKind.rb)
+ .id$('transit-kind-rb')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('S-Bahn').class$('product-suburban').att$('for', 'transit-kind-s')),
+ input('checkbox')
+ .checked$(transitKind.s)
+ .id$('transit-kind-s')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('U-Bahn').class$('product-subway').att$('for', 'transit-kind-u')),
+ input('checkbox')
+ .checked$(transitKind.u)
+ .id$('transit-kind-u')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('Tram, Stadtbahn').class$('product-tram').att$('for', 'transit-kind-tram')),
+ input('checkbox')
+ .checked$(transitKind.tram)
+ .id$('transit-kind-tram')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('Bus').class$('product-bus').att$('for', 'transit-kind-bus')),
+ input('checkbox')
+ .checked$(transitKind.bus)
+ .id$('transit-kind-bus')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ div(
+ p(label('Ferry').class$('product-ferry').att$('for', 'transit-kind-ferry')),
+ input('checkbox')
+ .checked$(transitKind.ferry)
+ .id$('transit-kind-ferry')
+ .class$('transit-kind')
+ .event$('change', transitKindCheckChanged),
+ ).class$('checkbox'),
+ h4('Start search'),
+ a('', 'Search').id$('search-link'),
+ ).class$('content')
+ document.body.insertBefore(contentDiv, footer)
+ contentDiv.style.display = 'flex'
+ contentDiv.style.flexDirection = 'column'
+
+ updateSearchLink()
+ }
+
+ 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()
+ }
+ })
+})
diff --git a/icons/star_empty.svg b/icons/star_empty.svg
new file mode 100644
index 0000000..1736e08
--- /dev/null
+++ b/icons/star_empty.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/icons/star_full.svg b/icons/star_full.svg
new file mode 100644
index 0000000..cb2231e
--- /dev/null
+++ b/icons/star_full.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/index.html b/index.html
index 3cbe8db..e99d883 100644
--- a/index.html
+++ b/index.html
@@ -4,31 +4,36 @@
-
InfoTren
+
InfoDTrain
+
+
+
diff --git a/index.js b/index.js
index 9559327..c966449 100644
--- a/index.js
+++ b/index.js
@@ -1,59 +1,76 @@
window.addEventListener('load', function (e) {
if (window.localStorage) {
- var recentViewTrain = localStorage.getItem('recent/view-train')
- if (recentViewTrain) {
+ var recentViewTrainStr = localStorage.getItem('recent/view-train')
+ if (recentViewTrainStr) {
/**
- * @property {string} trainNumber
+ * @type {object}
+ * @property {?string} trainNumber
+ * @property {?string} name
+ * @property {string} tripId
* @property {string} date
* @property {string} $addDate
- * @property {string | undefined} groupIndex
*/
- recentViewTrain = JSON.parse(recentViewTrain)
+ var recentViewTrain = JSON.parse(recentViewTrainStr)
var addDate = new Date(recentViewTrain.$addDate)
- addDate.setHours(addDate.getHours() + 2) // store recents for 2 hours
+ addDate.setHours(addDate.getHours() + 6) // store recents for 6 hours
if (addDate.getTime() > Date.now()) {
var recentViewTrainLi = document.createElement('li')
var recentViewTrainLink = document.createElement('a')
recentViewTrainLi.appendChild(recentViewTrainLink)
var recentViewTrainLinkUrl = new URL('/view-train.html', window.location.origin)
- recentViewTrainLinkUrl.searchParams.append('train', recentViewTrain.trainNumber)
+ recentViewTrainLinkUrl.searchParams.append('tripId', recentViewTrain.tripId)
recentViewTrainLinkUrl.searchParams.append('date', recentViewTrain.date)
- if (recentViewTrain.groupIndex) {
- recentViewTrainLinkUrl.searchParams.append('groupIndex', recentViewTrain.groupIndex)
- }
recentViewTrainLink.href = recentViewTrainLinkUrl.toString()
recentViewTrainLink.classList.add('items')
- recentViewTrainLink.innerText = `Recent train: ${recentViewTrain.trainNumber}`
+ recentViewTrainLink.innerText = `Recent train: ${recentViewTrain.name || "..."}`
+ if (recentViewTrain.trainNumber) {
+ recentViewTrainLink.innerText = `Recent train: ${recentViewTrain.name || "..."} (${recentViewTrain.trainNumber})`
+ }
- fetch(`https://scraper.infotren.dcdev.ro/v3/trains/${recentViewTrain.trainNumber}?date=${recentViewTrain.date}`)
+ fetch(`https://v6.db.transport.rest/trips/${encodeURI(recentViewTrain.tripId)}`)
.then(function (result) {
if (result.ok) {
return result.json()
}
+ else {
+ return Promise.reject('Response not okay')
+ }
})
.then(function (result) {
- recentViewTrainLink.innerText = 'Recent train: '
- trainIdSpan(result.rank, result.number, recentViewTrainLink)
- if (recentViewTrain.groupIndex !== undefined || result.groups.length === 1) {
- var group = result.groups[recentViewTrain.groupIndex || 0]
- if (group.status) {
- if (group.status.delay === 0) {
- recentViewTrainLink.appendChild(document.createTextNode(" (on time)"))
- }
- else if (group.status.delay > 0) {
- recentViewTrainLink.appendChild(document.createTextNode(` (${group.status.delay} min late)`))
- }
- else {
- recentViewTrainLink.appendChild(document.createTextNode(` (${-group.status.delay} min early)`))
- }
- }
- }
+ recentViewTrainLink.innerText = `Recent train: ${result.trip.line.name} (${result.trip.line.fahrtNr})`
})
var myTrainLi = document.getElementById("my-train-li")
myTrainLi.parentNode.insertBefore(recentViewTrainLi, myTrainLi.nextSibling)
}
}
+
+ var recentRouteStr = localStorage.getItem('recent/route')
+ if (recentRouteStr) {
+ /**
+ * @type {object}
+ * @property {string} queryParams
+ * @property {string} from
+ * @property {string} to
+ * @property {string} $addDate
+ */
+ var recentRoute = JSON.parse(recentRouteStr)
+ var addDate = new Date(recentRoute.$addDate)
+ addDate.setHours(addDate.getHours() + 12) // store recents for 6 hours
+ if (addDate.getTime() > Date.now()) {
+ var recentRouteLi = document.createElement('li')
+ var recentRouteLink = document.createElement('a')
+ recentRouteLi.appendChild(recentRouteLink)
+ var recentRouteLinkUrl = new URL('/config-route.html', window.location.origin)
+ recentRouteLinkUrl.search = recentRoute.queryParams
+ recentRouteLink.href = recentRouteLinkUrl.toString()
+ recentRouteLink.classList.add('items')
+ recentRouteLink.innerText = `Recent route: ${recentRoute.from} → ${recentRoute.to}`
+
+ var routesLi = document.getElementById("routes-li")
+ routesLi.parentNode.insertBefore(recentRouteLi, routesLi.nextSibling)
+ }
+ }
}
})
diff --git a/manifest.json b/manifest.json
index eb0eea4..44986ca 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,8 +1,7 @@
{
- "id": "ro.dcdev.infotren.kai",
- "name": "Info Tren: Romanian Railways",
- "short_name": "Info Tren",
- "description": "Web application for Informatica Feroviară scraper, showing data about Romanian Railways",
+ "name": "InfoDTrain",
+ "short_name": "InfoDTrain",
+ "description": "Web application for Deutsche Bahn API",
"theme_color": "#0000ff",
"background_color": "#ffffff",
"display": "standalone",
@@ -27,7 +26,7 @@
},
{
"name": "Train Routes",
- "url": "/route.html",
+ "url": "/config-route.html",
"description": "Plan an itinerary"
}
],
diff --git a/manifest.webapp b/manifest.webapp
index a954a30..48239eb 100644
--- a/manifest.webapp
+++ b/manifest.webapp
@@ -1,8 +1,8 @@
{
"version": "1",
- "name": "InfoTren",
+ "name": "InfoDTrain",
"launch_path": "/index.html",
- "description": "Frontend for InfoFer scraper",
+ "description": "Frontend for Deutsche Bahn API",
"developer": {
"name": "Dan Cojocaru",
"url": "https://dcdev.ro"
diff --git a/route.css b/route.css
index 2e5dcf3..c54a383 100644
--- a/route.css
+++ b/route.css
@@ -1,38 +1,47 @@
.itinerary-train {
display: grid;
- grid-template-columns: auto 1fr;
+ grid-template-columns: auto 1fr auto;
grid-template-rows: repeat(auto-fit, auto);
+ align-items: center;
}
.itinerary-train:not(:last-child) {
grid-template-areas:
- "dep-time dep-station"
- "train train"
- "arr-time arrdep-station"
- "dep2-time arrdep-station";
+ "dep-time dep-station dep-platform"
+ "train train train"
+ "arr-time arrdep-station arr-platform"
+ "dep2-time arrdep-station dep2-platform";
}
.itinerary-train:last-child {
grid-template-areas:
- "train train"
- "arr-time arr-station";
+ "train train train"
+ "arr-time arr-station arr-platform";
}
.itinerary-train:only-child {
grid-template-areas:
- "dep-time dep-station"
- "train train"
- "arr-time arr-station";
+ "dep-time dep-station dep-platform"
+ "train train train"
+ "arr-time arr-station arr-platform";
}
.itinerary-train .departure.time {
grid-area: dep-time;
}
+.itinerary-train .departure.platform {
+ grid-area: dep-platform;
+}
+
.itinerary-train .next-departure.time {
grid-area: dep2-time;
}
+.itinerary-train .next-departure.platform {
+ grid-area: dep2-platform;
+}
+
.itinerary-train .departure.station {
grid-area: dep-station;
}
@@ -45,6 +54,10 @@
grid-area: arr-time;
}
+.itinerary-train .arrival.platform {
+ grid-area: arr-platform;
+}
+
.itinerary-train:not(:last-child) .arrival.station {
grid-area: arrdep-station;
align-self: center;
@@ -57,9 +70,34 @@
.itinerary-train .time {
margin-left: 2px;
margin-right: 2px;
+ align-self: center;
+}
+
+.itinerary-train .platform {
+ margin: 2px;
+ padding: 2px;
+ border: 1px solid black;
+ border-radius: 4px;
+ justify-self: end;
+ min-width: 16px;
+ text-align: center;
+}
+
+.itinerary-train .platform.changed {
+ color: red;
+ border-color: red;
}
.train .company {
font-size: 0.8em;
font-style: italic;
}
+
+.walking {
+ font-size: 0.95em;
+ font-style: italic;
+}
+
+input#time {
+ margin: 1px;
+}
diff --git a/route.dark.css b/route.dark.css
new file mode 100644
index 0000000..b47aeff
--- /dev/null
+++ b/route.dark.css
@@ -0,0 +1,11 @@
+@media (prefers-color-scheme: dark) {
+ .itinerary-train .platform {
+ border-color: white;
+ }
+
+ .itinerary-train .platform.changed {
+ color: #ff3333;
+ border-color: #ff3333;
+ }
+
+}
diff --git a/route.html b/route.html
index 9801a28..8499421 100644
--- a/route.html
+++ b/route.html
@@ -4,15 +4,18 @@
-
Route - InfoTren
+
Route - InfoDTrain
+
+
+
diff --git a/route.js b/route.js
index 07dcc2c..76d3ef6 100644
--- a/route.js
+++ b/route.js
@@ -10,19 +10,32 @@ var toStation = null
* @type {Date | null}
*/
var departureDate = null
+var transitKind = {
+ ice: true,
+ ic: true,
+ re: true,
+ rb: true,
+ s: true,
+ bus: true,
+ ferry: true,
+ u: true,
+ tram: true,
+}
/**
- * @type {{ name: string, stoppedAtBy: string[] }[]}
+ * @type {{id: string, name: string}[]}
*/
var knownStations = []
-function goToStation(station) {
+var itineraries = null
+
+function goToStation(stationId) {
var url = new URL(window.location.href)
if (!fromStation) {
- url.searchParams.set('from', station)
+ url.searchParams.set('from', stationId)
}
else if (!toStation) {
- url.searchParams.set('to', station)
+ url.searchParams.set('to', stationId)
}
// url.searchParams.set('date', new Date().toISOString())
window.location.href = url.toString()
@@ -62,41 +75,9 @@ function rebuildSuggestions() {
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);
- }
+ var suggestions = knownStations.slice()
- 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)
@@ -106,7 +87,7 @@ function rebuildSuggestions() {
suggestionLi.style.padding = '2px 0'
function onAction(e) {
- goToStation(suggestion.name)
+ goToStation(JSON.stringify(suggestion))
}
suggestionLi.addEventListener('click', onAction)
suggestionLi.addEventListener('keypress', function (e) {
@@ -121,7 +102,7 @@ function rebuildSuggestions() {
var stationNameP = document.createElement('p')
suggestionLi.appendChild(stationNameP)
- stationNameP.textContent = suggestion.name
+ stationNameP.textContent = suggestion.name || suggestion.address
stationNameP.classList.add('pri', 'stationName')
// var trainCompanyP = document.createElement('p')
@@ -131,33 +112,6 @@ function rebuildSuggestions() {
// 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
@@ -167,31 +121,58 @@ function rebuildSuggestions() {
}, 500)
}
+var fetchAbortController = new AbortController()
+function reloadSuggestions() {
+ var stationNameInput = document.getElementById('stationName')
+ var stationName = searchNormalize(stationNameInput.value.trim())
+
+ var locationsUrl = new URL('https://v6.db.transport.rest/locations')
+ locationsUrl.searchParams.set('query', stationName)
+ locationsUrl.searchParams.set('limit', '25')
+ locationsUrl.searchParams.set('fuzzy', 'true')
+ locationsUrl.searchParams.set('stops', 'true')
+ locationsUrl.searchParams.set('addresses', 'true')
+ locationsUrl.searchParams.set('poi', 'true')
+
+ fetchAbortController.abort()
+ fetchAbortController = new AbortController()
+ fetch(locationsUrl.toString(), { signal: fetchAbortController.signal })
+ .then(function (response) {
+ return response.json()
+ })
+ .then(function (data) {
+ if (data) {
+ knownStations = Object.values(data)
+ rebuildSuggestions()
+ }
+ })
+}
+
/**
- * @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
+ * @typedef DbJourney
+ * @prop {'journey'} type
+ * @prop {(DbTrip & DbArrDep & {tripId: string})[]} legs
+ * @prop {string} refreshToken
+ * @prop {DbRemark[]} remarks
*/
+
/**
- * @param {Itinerary[]} data
+ * @param {{journeys: DbJourney[]}} data
*/
function onItineraries(data) {
- var contentDiv = document.createElement('div')
- document.body.insertBefore(contentDiv, document.querySelector('footer'))
- contentDiv.classList.add('content')
+ var contentDiv = document.getElementById('content-div')
+ if (!contentDiv) {
+ contentDiv = document.createElement('div')
+ document.body.insertBefore(contentDiv, document.querySelector('footer'))
+ contentDiv.classList.add('content')
+ contentDiv.id = 'content-div'
+ }
+
+ while (contentDiv.childNodes.length > 0) {
+ contentDiv.childNodes[contentDiv.childNodes.length - 1].remove()
+ }
- for (var i = 0; i < data.length; i++) {
+ for (var i = 0; i < data.journeys.length; i++) {
var itineraryDiv = document.createElement('div')
contentDiv.appendChild(itineraryDiv)
@@ -203,8 +184,8 @@ function onItineraries(data) {
itineraryDiv.appendChild(trainsDiv)
trainsDiv.classList.add('itinerary-trains')
- data[i].trains.forEach(function (train, idx) {
- var last = idx === data[i].trains.length - 1
+ data.journeys[i].legs.forEach(function (train, idx) {
+ var last = idx === data.journeys[i].legs.length - 1
var trainDiv = document.createElement('div')
trainsDiv.appendChild(trainDiv)
@@ -216,64 +197,127 @@ function onItineraries(data) {
departureTimeP.classList.add('sec', 'departure', 'time')
var departureTimePre = document.createElement('pre')
departureTimeP.appendChild(departureTimePre)
- var departure = new Date(train.departureDate)
+ var departure = new Date(train.plannedDeparture)
departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
var departureHeading = document.createElement('h3')
trainDiv.appendChild(departureHeading)
departureHeading.classList.add('departure', 'station')
- var departureLink = document.createElement('a')
- departureHeading.appendChild(departureLink)
- departureLink.textContent = train.from
- departureLink.classList.add('no-custom-a', 'items')
- var departureUrl = new URL('/view-station.html', window.location.origin)
- departureUrl.searchParams.set('station', train.from)
- departureLink.href = departureUrl.toString()
+ if (train.origin.type === 'stop' || train.origin.type === 'station') {
+ var departureLink = document.createElement('a')
+ departureHeading.appendChild(departureLink)
+ departureLink.textContent = train.origin.name
+ departureLink.classList.add('no-custom-a', 'items')
+ var departureUrl = new URL('/view-station.html', window.location.origin)
+ departureUrl.searchParams.set('stationId', train.origin.id)
+ departureLink.href = departureUrl.toString()
+ }
+ else {
+ var departureSpan = document.createElement('span')
+ departureHeading.append(departureSpan)
+ departureSpan.innerText = train.origin.name || train.origin.address
+ }
+
+ if (train.departurePlatform || train.plannedDeparturePlatform) {
+ var departurePlatformP = document.createElement('p')
+ trainDiv.append(departurePlatformP)
+ departurePlatformP.classList.add('sec', 'departure', 'platform')
+ if (train.departurePlatform && train.departurePlatform != train.plannedDeparturePlatform) {
+ departurePlatformP.classList.add('changed')
+ }
+ departurePlatformP.textContent = `${train.departurePlatform || train.plannedDeparturePlatform}`
+ }
}
var trainP = document.createElement('p')
trainDiv.appendChild(trainP)
trainP.classList.add('pri', 'train')
- var trainLink = document.createElement('a')
- trainP.appendChild(trainLink)
- trainIdSpan(train.trainRank, train.trainNumber, trainLink)
- trainLink.classList.add('no-custom-a', 'items')
- var trainUrl = new URL('/view-train.html', window.location.origin)
- trainUrl.searchParams.set('train', train.trainNumber)
- trainLink.href = trainUrl.toString()
- trainP.appendChild(document.createTextNode(' '))
- var trainCompany = document.createElement('span')
- trainP.appendChild(trainCompany)
- trainCompany.textContent = '(' + train.operator + ')'
- trainCompany.classList.add('company')
+ if (!train.walking) {
+ var trainLink = document.createElement('a')
+ trainP.appendChild(trainLink)
+ trainLink.innerText = train.line.name
+ trainLink.classList.add('no-custom-a', 'items')
+ if (train.line.product) {
+ if (train.line.productName === 'STB' && train.line.name.startsWith('STB U')) {
+ train.line.product = 'subway'
+ }
+ trainLink.classList.add('product-' + train.line.product)
+ }
+ var trainUrl = new URL('/view-train.html', window.location.origin)
+ trainUrl.searchParams.set('tripId', train.tripId)
+ trainUrl.searchParams.set('startId', train.origin.id)
+ trainUrl.searchParams.set('stopId', train.destination.id)
+ trainLink.href = trainUrl.toString()
+ trainP.appendChild(document.createTextNode(' '))
+ if (train.line.operator) {
+ var trainCompany = document.createElement('span')
+ trainP.appendChild(trainCompany)
+ trainCompany.textContent = '(' + train.line.operator.name + ')'
+ trainCompany.classList.add('company')
+ }
+ }
+ else {
+ var walkingSpan = document.createElement('span')
+ trainP.append(walkingSpan)
+ walkingSpan.classList.add('walking')
+ walkingSpan.innerText = `Walking (${train.distance} m)`
+ }
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)
+ var arrival = new Date(train.plannedArrival)
arrivalTimePre.textContent = arrival.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
var arrivalHeading = document.createElement('h3')
trainDiv.appendChild(arrivalHeading)
arrivalHeading.classList.add('arrival', 'station')
- var arrivalLink = document.createElement('a')
- arrivalHeading.appendChild(arrivalLink)
- arrivalLink.textContent = train.to
- arrivalLink.classList.add('no-custom-a', 'items')
- var arrivalUrl = new URL('/view-station.html', window.location.origin)
- arrivalUrl.searchParams.set('station', train.from)
- arrivalLink.href = arrivalUrl.toString()
+ if (train.destination.type === 'stop' || train.destination.type === 'station') {
+ var arrivalLink = document.createElement('a')
+ arrivalHeading.appendChild(arrivalLink)
+ arrivalLink.textContent = train.destination.name
+ arrivalLink.classList.add('no-custom-a', 'items')
+ var arrivalUrl = new URL('/view-station.html', window.location.origin)
+ arrivalUrl.searchParams.set('stationId', train.destination.id)
+ arrivalLink.href = arrivalUrl.toString()
+ }
+ else {
+ var arrivalSpan = document.createElement('span')
+ arrivalHeading.append(arrivalSpan)
+ arrivalSpan.innerText = train.destination.name || train.destination.address
+ }
+
+ if (train.arrivalPlatform || train.plannedArrivalPlatform) {
+ var arrivalPlatformP = document.createElement('p')
+ trainDiv.append(arrivalPlatformP)
+ arrivalPlatformP.classList.add('sec', 'arrival', 'platform')
+ if (train.arrivalPlatform && train.arrivalPlatform != train.plannedArrivalPlatform) {
+ arrivalPlatformP.classList.add('changed')
+ }
+ arrivalPlatformP.textContent = `${train.arrivalPlatform || train.plannedArrivalPlatform}`
+ }
if (!last) {
+ var nextTrain = data.journeys[i].legs[idx + 1]
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)
+ var departure = new Date(nextTrain.plannedDeparture)
departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
+
+ if (nextTrain.departurePlatform || nextTrain.plannedDeparturePlatform) {
+ var departurePlatformP = document.createElement('p')
+ trainDiv.append(departurePlatformP)
+ departurePlatformP.classList.add('sec', 'next-departure', 'platform')
+ if (nextTrain.departurePlatform && nextTrain.departurePlatform != nextTrain.plannedDeparturePlatform) {
+ departurePlatformP.classList.add('changed')
+ }
+ departurePlatformP.textContent = `${nextTrain.departurePlatform || nextTrain.plannedDeparturePlatform}`
+ }
}
})
}
@@ -299,7 +343,9 @@ function csk() {
window.addEventListener('load', function (e) {
var sp = new URL(window.location.href).searchParams
fromStation = sp.get('from')
+ var fromJson = JSON.parse(fromStation || 'null')
toStation = sp.get('to')
+ var toJson = JSON.parse(toStation || 'null')
var departureDateStr = sp.get('departureDate')
if (departureDateStr) {
departureDate = new Date(departureDateStr)
@@ -316,30 +362,46 @@ window.addEventListener('load', function (e) {
titleH1.textContent = 'Find Route - Departure Date'
}
else {
- titleH1.textContent = `${fromStation} - ${toStation}`
+ titleH1.textContent = `${fromJson.name || fromJson.address} - ${toJson.name || toJson.address}`
+ }
+
+ var transitKindJson = JSON.parse(sp.get('transitKind'))
+ if (transitKindJson) {
+ transitKind = transitKindJson
}
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'
+ document.body.insertBefore(
+ h4(
+ label('Station Name').att$('for', 'stationName'),
+ ),
+ footer,
+ )
+ // 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'
+
+ document.body.insertBefore(
+ input('search').id$('stationName').att$('name', 'stationName').class$('items'),
+ footer,
+ )
+ // var stationNameInput = document.createElement('input')
+ // document.body.insertBefore(stationNameInput, footer)
+ // stationNameInput.type = 'search'
+ // stationNameInput.classList.add('items')
+ // stationNameInput.name = 'stationName'
+ // stationNameInput.id = 'stationName'
+
+ document.body.insertBefore(h4('Suggestions'), footer)
+ // var suggestionsH4 = document.createElement('h4')
+ // document.body.insertBefore(suggestionsH4, footer)
+ // suggestionsH4.textContent = 'Suggestions'
var contentDiv = document.createElement('div')
document.body.insertBefore(contentDiv, footer)
@@ -352,7 +414,7 @@ window.addEventListener('load', function (e) {
var stationName = document.getElementById('stationName')
stationName.addEventListener('input', function (e) {
- rebuildSuggestions()
+ reloadSuggestions()
})
stationName.addEventListener('focus', function (e) {
focusedElement = stationName
@@ -368,19 +430,6 @@ window.addEventListener('load', function (e) {
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')
@@ -394,7 +443,7 @@ window.addEventListener('load', function (e) {
contentDiv.appendChild(departureDateUl)
departureDateUl.id = 'suggestionsArea'
- for (var i = 0, departureOption = new Date(); i < 30; i++, departureOption.setDate(departureOption.getDate() + 1)) {
+ for (var i = -1, departureOption = (function () { var d = new Date(); d.setDate(d.getDate() - 1); return d })(); i < 30; i++, departureOption.setDate(departureOption.getDate() + 1)) {
var suggestionLi = document.createElement('li')
departureDateUl.appendChild(suggestionLi)
suggestionLi.classList.add('items')
@@ -414,6 +463,9 @@ window.addEventListener('load', function (e) {
suggestionLi.addEventListener('focus', function (e) {
focusedElement = suggestionLi
})
+ if (i === 0) {
+ suggestionLi.focus()
+ }
})()
var innerP = document.createElement('p')
suggestionLi.appendChild(innerP)
@@ -421,6 +473,9 @@ window.addEventListener('load', function (e) {
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')}`
+ if (i === 0) {
+ innerPre.textContent += ' (today)'
+ }
}
document.querySelector('.csk').textContent = 'Select'
@@ -439,11 +494,51 @@ window.addEventListener('load', function (e) {
loadingP.classList.add('pri')
loadingP.textContent = 'Loading data...'
+ var url = new URL('https://v6.db.transport.rest/journeys')
+ if (fromJson.type === 'stop' || fromJson.type === 'station') {
+ url.searchParams.set('from', fromJson.id)
+ }
+ else {
+ if (fromJson.type === 'location') {
+ delete fromJson.id
+ }
+ Object.keys(fromJson).forEach(function (key) {
+ url.searchParams.set(`from.${key}`, fromJson[key])
+ })
+ }
+ if (toJson.type === 'stop' || toJson.type === 'station') {
+ url.searchParams.set('to', toJson.id)
+ }
+ else {
+ if (toJson.type === 'location') {
+ delete toJson.id
+ }
+ Object.keys(toJson).forEach(function (key) {
+ url.searchParams.set(`to.${key}`, toJson[key])
+ })
+ }
+ url.searchParams.set('departure', departureDate.toISOString())
+ url.searchParams.set('results', '20')
+ url.searchParams.set('stopovers', 'true')
+ url.searchParams.set('nationalExpress', transitKind.ice)
+ url.searchParams.set('national', transitKind.ic)
+ url.searchParams.set('regionalExpress', transitKind.re)
+ url.searchParams.set('regional', transitKind.rb)
+ url.searchParams.set('suburban', transitKind.s)
+ url.searchParams.set('bus', transitKind.bus)
+ url.searchParams.set('ferry', transitKind.ferry)
+ url.searchParams.set('subway', transitKind.u)
+ url.searchParams.set('tram', transitKind.tram)
+
+ if (window.localStorage) {
+ this.localStorage.setItem('recent/route', JSON.stringify({
+ $addDate: new Date().toISOString(),
+ from: fromJson.name || fromJson.address,
+ to: toJson.name || toJson.address,
+ queryParams: new URL(window.location.href).search,
+ }))
+ }
- 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()
@@ -451,6 +546,34 @@ window.addEventListener('load', function (e) {
.then(function (data) {
contentDiv.remove()
onItineraries(data)
+ itineraries = data
+ function fetchMore() {
+ console.debug(`Got ${itineraries.journeys.length} journeys, fetching more`)
+ var moreUrl = new URL(url.toString())
+ moreUrl.searchParams.delete('departure')
+ moreUrl.searchParams.set('laterThan', itineraries.laterRef)
+ fetch(moreUrl.toString())
+ .then(function (result) {
+ return result.json()
+ })
+ .then(function (data) {
+ if (data.journeys) {
+ itineraries.journeys = itineraries.journeys.concat(data.journeys)
+ itineraries.laterRef = data.laterRef
+ onItineraries(itineraries)
+
+ if (itineraries.laterRef) {
+ var lastJourney = itineraries.journeys[itineraries.journeys.length - 1]
+ var lastLeg = lastJourney.legs[lastJourney.legs.length - 1]
+ var departureDate = new Date(lastLeg.plannedDeparture)
+ if (departureDate.getTime() - Date.now() < 86400000) {
+ setTimeout(fetchMore, 500)
+ }
+ }
+ }
+ })
+ }
+ fetchMore()
})
.catch(function (e) {
loadingP.textContent = 'An error has occured'
diff --git a/showcase.html b/showcase.html
index 0954ffc..b13ca82 100644
--- a/showcase.html
+++ b/showcase.html
@@ -7,6 +7,7 @@
Showcase
+
diff --git a/station.html b/station.html
index 7c2b858..c5d2580 100644
--- a/station.html
+++ b/station.html
@@ -3,12 +3,14 @@
+
-
Station - InfoTren
+
Station - InfoDTrain
+
diff --git a/station.js b/station.js
index 7d65211..240e4a8 100755
--- a/station.js
+++ b/station.js
@@ -1,9 +1,9 @@
var knownStations = []
-function goToStation(station) {
+function goToStation(stationId) {
var url = new URL(window.location.href)
url.pathname = 'view-station.html'
- url.searchParams.set('station', station)
+ url.searchParams.set('stationId', stationId)
url.searchParams.set('date', new Date().toISOString())
window.location.href = url.toString()
}
@@ -36,41 +36,9 @@ function rebuildSuggestions() {
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;
- }
+ var suggestions = knownStations.slice()
- 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)
@@ -80,7 +48,7 @@ function rebuildSuggestions() {
suggestionLi.style.padding = '2px 0'
function onAction(e) {
- goToStation(suggestion.name)
+ goToStation(suggestion.id)
}
suggestionLi.addEventListener('click', onAction)
suggestionLi.addEventListener('keypress', function (e) {
@@ -105,33 +73,6 @@ function rebuildSuggestions() {
// 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
@@ -141,6 +82,31 @@ function rebuildSuggestions() {
}, 500)
}
+var fetchAbortController = new AbortController()
+function reloadSuggestions() {
+ var stationNameInput = document.getElementById('stationName')
+ var stationName = searchNormalize(stationNameInput.value.trim())
+
+ var locationsUrl = new URL('https://v6.db.transport.rest/locations')
+ locationsUrl.searchParams.set('query', stationName)
+ locationsUrl.searchParams.set('limit', '25')
+ locationsUrl.searchParams.set('fuzzy', 'true')
+ locationsUrl.searchParams.set('stops', 'true')
+
+ fetchAbortController.abort()
+ fetchAbortController = new AbortController()
+ fetch(locationsUrl.toString(), { signal: fetchAbortController.signal })
+ .then(function (response) {
+ return response.json()
+ })
+ .then(function (data) {
+ if (data) {
+ knownStations = Object.values(data)
+ rebuildSuggestions()
+ }
+ })
+}
+
function lsk() {
document.getElementById('stationName').focus()
}
@@ -161,7 +127,7 @@ function csk() {
window.addEventListener('load', function (e) {
var stationName = document.getElementById('stationName')
stationName.addEventListener('input', function (e) {
- rebuildSuggestions()
+ reloadSuggestions()
})
stationName.addEventListener('focus', function (e) {
focusedElement = stationName
@@ -197,15 +163,5 @@ window.addEventListener('load', function (e) {
}
})
- 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()
- })
+ reloadSuggestions()
})
diff --git a/sw.js b/sw.js
index a0f4573..2c32cf5 100755
--- a/sw.js
+++ b/sw.js
@@ -1,4 +1,4 @@
-const VERSION = 'v34'
+const VERSION = 'v14'
const API_ORIGIN = 'https://scraper.infotren.dcdev.ro/'
const API_TRAINS = `${API_ORIGIN}v3/trains`
const API_STATIONS = `${API_ORIGIN}v3/stations`
@@ -20,6 +20,7 @@ const CACHE_FIRST = [
// Base
'/base.css',
+ '/base.dark.css',
// Pages
'/index.html',
@@ -34,6 +35,7 @@ const CACHE_FIRST = [
'/view-train.html',
'/view-train.js',
'/view-train.css',
+ '/view-train.dark.css',
'/station.html',
'/station.js',
@@ -41,10 +43,16 @@ const CACHE_FIRST = [
'/view-station.html',
'/view-station.js',
'/view-station.css',
+ '/view-station.dark.css',
+
+ '/config-route.html',
+ '/config-route.js',
+ '/config-route.css',
'/route.html',
'/route.js',
'/route.css',
+ '/route.dark.css',
// API
API_TRAINS,
diff --git a/train.html b/train.html
index cd4cca5..e1ad8fd 100644
--- a/train.html
+++ b/train.html
@@ -3,12 +3,14 @@
+
-
Train - InfoTren
+
Train - InfoDTrain
+
diff --git a/train.js b/train.js
index 2de4c5c..6538841 100755
--- a/train.js
+++ b/train.js
@@ -1,9 +1,9 @@
var knownTrains = []
-function goToTrain(number) {
+function goToTrip(tripId) {
var url = new URL(window.location.href)
url.pathname = 'view-train.html'
- url.searchParams.set('train', number)
+ url.searchParams.set('tripId', tripId)
url.searchParams.set('date', new Date().toISOString())
window.location.href = url.toString()
}
@@ -26,43 +26,16 @@ function rebuildSuggestions() {
suggestionsArea.childNodes[0].remove()
}
- var trainNumberInput = document.getElementById('trainNumber')
- var trainNumber = trainNumberInput.value.trim()
-
- var suggestions = []
- if (!trainNumber) {
- suggestions = knownTrains.slice()
- }
- else {
- for (var i = 0; i < knownTrains.length; i++) {
- if (!knownTrains[i].number.includes(trainNumber)) {
- continue
- }
- suggestions.push(knownTrains[i])
- }
- suggestions.sort((s1, s2) => {
- if (s1.number.indexOf(trainNumber) != s2.number.indexOf(trainNumber)) {
- return s1.number.indexOf(trainNumber) - s2.number.indexOf(trainNumber);
- }
-
- if (s1.number.length != s2.number.length) {
- return s1.number.length - s2.number.length;
- }
-
- return s1.number.localeCompare(s2.number);
- })
- }
+ var suggestions = knownTrains.filter(function (suggestion) {
+ return suggestion.line.name
+ })
// Trim the amount of results displayed
if (suggestions.length > 100) {
suggestions.splice(100)
}
- var foundInput = false
suggestions.forEach(function (suggestion, index) {
- if (trainNumber == suggestion.number) {
- foundInput = true
- }
var suggestionLi = document.createElement('li')
suggestionsArea.appendChild(suggestionLi)
@@ -72,7 +45,7 @@ function rebuildSuggestions() {
suggestionLi.style.padding = '2px 0'
function onAction(e) {
- goToTrain(suggestion.number)
+ goToTrip(suggestion.id)
}
suggestionLi.addEventListener('click', onAction)
suggestionLi.addEventListener('keypress', function (e) {
@@ -86,44 +59,22 @@ function rebuildSuggestions() {
var trainNameP = document.createElement('p')
suggestionLi.appendChild(trainNameP)
-
- trainIdSpan(suggestion.rank, suggestion.number, trainNameP)
+ trainNameP.textContent = `${suggestion.line.name} (${suggestion.line.fahrtNr})`
trainNameP.classList.add('pri', 'trainName')
+ var trainRouteP = document.createElement('p')
+ suggestionLi.appendChild(trainRouteP)
+
+ trainRouteP.textContent = `${suggestion.origin.name} → ${suggestion.destination.name}`
+ trainRouteP.classList.add('thi')
+
var trainCompanyP = document.createElement('p')
suggestionLi.appendChild(trainCompanyP)
- trainCompanyP.textContent = suggestion.company
+ trainCompanyP.textContent = suggestion.line.operator.name
trainCompanyP.classList.add('thi')
}, 0)
})
- if (!foundInput && trainNumber) {
- 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) {
- goToTrain(trainNumber)
- }
- suggestionLi.addEventListener('click', onAction)
- suggestionLi.addEventListener('keypress', function (e) {
- if (e.key == 'Enter') {
- onAction(e)
- }
- })
- suggestionLi.addEventListener('focus', function (e) {
- focusedElement = suggestionLi
- })
-
- var trainNameP = document.createElement('p')
- suggestionLi.appendChild(trainNameP)
-
- trainNameP.textContent = `Train ${trainNumber}`
- trainNameP.classList.add('pri', 'trainName')
- }
setTimeout(function () {
_rebuildDebounce = null
@@ -133,6 +84,34 @@ function rebuildSuggestions() {
}, 500)
}
+var fetchAbortController = new AbortController()
+function reloadSuggestions() {
+ var trainNumberInput = document.getElementById('trainNumber')
+ var trainNumber = trainNumberInput.value.trim()
+
+ var tripsUrl = new URL('https://v6.db.transport.rest/trips')
+ tripsUrl.searchParams.set('query', trainNumber)
+ tripsUrl.searchParams.set('onlyCurrentlyRunning', 'true')
+
+ fetchAbortController.abort()
+ fetchAbortController = new AbortController()
+ fetch(tripsUrl.toString(), { signal: fetchAbortController.signal })
+ .then(function (response) {
+ return response.json()
+ })
+ .then(function (data) {
+ if (data.trips) {
+ knownTrains = data.trips
+ knownTrains.sort(function (s1, s2) {
+ var diff1 = Math.abs(Date.now() - new Date(s1.plannedDeparture).getTime())
+ var diff2 = Math.abs(Date.now() - new Date(s2.plannedDeparture).getTime())
+ return diff1 - diff2
+ })
+ rebuildSuggestions()
+ }
+ })
+}
+
function lsk() {
document.getElementById('trainNumber').focus()
}
@@ -143,7 +122,7 @@ function csk() {
}
if (focusedElement.id === 'trainNumber') {
- goToTrain(document.activeElement.value.trim())
+ goToTrip(document.activeElement.value.trim())
}
else {
focusedElement.click()
@@ -153,7 +132,7 @@ function csk() {
window.addEventListener('load', function (e) {
var trainNumber = document.getElementById('trainNumber')
trainNumber.addEventListener('input', function (e) {
- rebuildSuggestions()
+ reloadSuggestions()
})
trainNumber.addEventListener('focus', function (e) {
focusedElement = trainNumber
@@ -166,7 +145,7 @@ window.addEventListener('load', function (e) {
})
trainNumber.addEventListener('keypress', function (e) {
if (e.key == 'Enter') {
- goToTrain(trainNumber.value.trim())
+ goToTrip(trainNumber.value.trim())
}
})
@@ -189,15 +168,5 @@ window.addEventListener('load', function (e) {
}
})
- 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()
- })
+ reloadSuggestions()
})
diff --git a/view-station.css b/view-station.css
index ce0b8be..0d7a9c2 100644
--- a/view-station.css
+++ b/view-station.css
@@ -53,12 +53,12 @@
.train-item {
display: grid;
- grid-template-columns: 30px 60px auto 1fr auto;
+ grid-template-columns: 100px 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";
+ "train time terminus platform"
+ "train delay terminus platform"
+ "status status status status";
align-items: center;
padding: 4px 0;
@@ -106,10 +106,19 @@
padding: 1px;
margin: 1px;
border-radius: 5px;
- aspect-ratio: 1 / 1;
+ /* aspect-ratio: 1 / 1; */
min-width: 22px;
display: flex;
justify-content: center;
align-items: center;
-}
\ No newline at end of file
+}
+
+.train-item .platform.changed {
+ color: red;
+ border-color: red;
+}
+
+.status-cancel {
+ text-align: start;
+}
diff --git a/view-station.dark.css b/view-station.dark.css
new file mode 100644
index 0000000..e7fb48f
--- /dev/null
+++ b/view-station.dark.css
@@ -0,0 +1,43 @@
+@media(prefers-color-scheme: dark) {
+
+ .early {
+ color: lightgreen;
+ }
+
+ .late {
+ color: #ff3333;
+ }
+
+ #tabs-arr {
+ border-bottom-color: #bbffbb;
+ }
+
+ #tabs-dep {
+ border-bottom-color: #bbbbff;
+ }
+
+ #arrivals .train-item {
+ background-color: #001a00;
+ }
+
+ #arrivals .train-item:nth-of-type(even) {
+ background-color: #004400;
+ }
+
+ #departures .train-item {
+ background-color: #00002a;
+ }
+
+ #departures .train-item:nth-of-type(even) {
+ background-color: #000066;
+ }
+
+ .train-item.cancelled {
+ background-color: #550000 !important;
+ }
+
+ .train-item .platform {
+ border-color: white;
+ }
+
+}
diff --git a/view-station.html b/view-station.html
index a3f180b..baace16 100644
--- a/view-station.html
+++ b/view-station.html
@@ -4,12 +4,14 @@
-
View Station - InfoTren
+
View Station - InfoDTrain
+
+
diff --git a/view-station.js b/view-station.js
index 153780a..3105ce9 100644
--- a/view-station.js
+++ b/view-station.js
@@ -1,24 +1,54 @@
-var station
+var stationId
var date
-var stationData = null
+var stationData = {
+ departures: [],
+ arrivals: [],
+}
var lastSuccessfulFetch = null
+/**
+ * @typedef StationArrDep
+ * @property {string} tripId
+ * @property {DbStop} stop
+ * @property {string | null} when
+ * @property {string} plannedWhen
+ * @property {number | null} delay
+ * @property {string | null} platform
+ * @property {string | null} plannedPlatform
+ * @property {string | null} prognosisType
+ * @property {string | null} direction
+ * @property {string | null} provenance
+ * @property {DbLine} line
+ * @property {DbRemark[]} remarks
+ * @property {DbStop | null} origin
+ * @property {DbStop | null} destionation
+ */
+
+/**
+ * @typedef StationData
+ * @prop {StationArrDep[]} departures
+ * @prop {StationArrDep[]} arrivals
+ */
+
+/**
+ * @param {?StationData} data
+ */
function onStationData(data) {
- if (!data) {
+ if (!data || !data.arrivals && !data?.departures) {
return
}
var title = document.getElementById('title')
- title.textContent = data.stationName
+ title.textContent = (data.departures[0] || data.arrivals[0]).stop.name
- document.getElementById('date').textContent = data.date
+ // document.getElementById('date').textContent = data.date
document.getElementById('loading').classList.add('hidden')
/**
* @param {HTMLElement} elem
- * @param {any[]} trains
+ * @param {StationArrDep[]} trains
*/
function addTrains(elem, trains) {
while (elem.childNodes.length > 0) {
@@ -32,26 +62,26 @@ function onStationData(data) {
var trainItem = document.createElement('li')
trainsList.appendChild(trainItem)
trainItem.classList.add('train-item')
- if (train.status && train.status.cancelled) {
- trainItem.classList.add('cancelled')
- }
+ // if (train.status && train.status.cancelled) {
+ // trainItem.classList.add('cancelled')
+ // }
var timeDiv = document.createElement('p')
trainItem.appendChild(timeDiv)
timeDiv.classList.add('pri', 'time')
var timeDivPre = document.createElement('pre')
timeDiv.appendChild(timeDivPre)
- timeDivPre.textContent = new Date(train.time).toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
+ timeDivPre.textContent = new Date(train.plannedWhen).toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' })
- if (train.status && train.status.delay != 0) {
+ if (train.delay && train.delay != 0) {
var delayDiv = document.createElement('p')
trainItem.appendChild(delayDiv)
delayDiv.classList.add('thi', 'delay')
- delayDiv.textContent = `${train.status.delay} min `
+ delayDiv.textContent = `${train.delay / 60} min `
// delayDiv.appendChild(document.createElement('br'))
var descSpan = document.createElement('span')
delayDiv.appendChild(descSpan)
- if (train.status.delay > 0) {
+ if (train.delay > 0) {
descSpan.classList.add('late')
descSpan.textContent = 'late'
}
@@ -61,12 +91,12 @@ function onStationData(data) {
}
}
- var rankDiv = document.createElement('p')
- trainItem.appendChild(rankDiv)
- rankDiv.classList.add('sec', 'rank', train.train.rank)
- var rankDivPre = document.createElement('pre')
- rankDiv.appendChild(rankDivPre)
- rankDivPre.textContent = train.train.rank
+ // var rankDiv = document.createElement('p')
+ // trainItem.appendChild(rankDiv)
+ // rankDiv.classList.add('sec', 'rank', train.train.rank)
+ // var rankDivPre = document.createElement('pre')
+ // rankDiv.appendChild(rankDivPre)
+ // rankDivPre.textContent = train.train.rank
var trainDiv = document.createElement('p')
trainItem.appendChild(trainDiv)
@@ -75,32 +105,35 @@ function onStationData(data) {
trainDiv.appendChild(trainDivHref)
trainDivHref.classList.add('no-a-custom')
var trainUrl = new URL('/view-train.html', window.location.origin)
- trainUrl.searchParams.append('train', train.train.number)
- trainUrl.searchParams.append('date', train.train.departureDate)
+ trainUrl.searchParams.append('tripId', train.tripId)
trainDivHref.href = trainUrl.toString()
var trainDivHrefPre = document.createElement('pre')
trainDivHref.appendChild(trainDivHrefPre)
- trainDivHrefPre.textContent = train.train.number
+ trainDivHrefPre.textContent = train.line.name
var terminusDiv = document.createElement('p')
trainItem.appendChild(terminusDiv)
terminusDiv.classList.add('pri', 'terminus')
- terminusDiv.textContent = train.train.terminus
+ terminusDiv.textContent = train.direction
- if (train.status && train.status.platform) {
+ if (train.platform) {
var platformDiv = document.createElement('div')
trainItem.appendChild(platformDiv)
platformDiv.classList.add('thi', 'platform')
+ if (train.platform && train.platform !== train.plannedPlatform) {
+ platformDiv.classList.add('changed')
+ }
var platformDivPre = document.createElement('pre')
platformDiv.appendChild(platformDivPre)
- platformDivPre.textContent = train.status.platform
+ platformDivPre.textContent = train.platform || train.plannedPlatform
}
- if (train.status && train.status.cancelled) {
+ if (train.cancelled) {
+ trainItem.classList.add('cancelled')
var statusDiv = document.createElement('p')
trainItem.appendChild(statusDiv)
- statusDiv.classList.add('sec', 'status')
- statusDiv.textContent = 'This train is cancelled'
+ statusDiv.classList.add('sec', 'status', 'status-cancel')
+ statusDiv.textContent = 'This journey is cancelled'
}
})
}
@@ -119,34 +152,44 @@ function refresh() {
}, timeout || 90000)
}
var reqDate = new Date(date.valueOf())
- reqDate.setMinutes(0, 0, 0)
- return fetch(
- `https://scraper.infotren.dcdev.ro/v3/stations/${station}?date=${reqDate.toISOString()}`,
- {
- cache: 'no-store',
- },
- ).then(function (response) {
- if (!response.ok) {
+ // reqDate.setHours(0, 0, 0, 0)
+ reqDate.setMinutes(reqDate.getMinutes() - 15)
+ return Promise.all([
+ fetch(
+ `https://v6.db.transport.rest/stops/${stationId}/arrivals?when=${reqDate.toISOString()}&duration=1440`,
+ {
+ cache: 'no-store',
+ },
+ ),
+ fetch(
+ `https://v6.db.transport.rest/stops/${stationId}/departures?when=${reqDate.toISOString()}&duration=1440`,
+ {
+ cache: 'no-store',
+ },
+ ),
+ ]).then(function (responses) {
+ if (!responses[0].ok || !responses[1].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) {
+ var cacheDate = responses[0].headers.get('SW-Cached-At')
+ return Promise.all(responses.map(function (r) {return r.json() })).then(function (data) {
data['$cacheDate'] = cacheDate
return data
})
- }).then(function (response) {
- if (!response) {
+ }).then(function (responses) {
+ if (!responses) {
return
}
- var cacheDate = response['$cacheDate']
+ var cacheDate = responses['$cacheDate']
if (cacheDate) {
cacheDate = new Date(cacheDate)
}
var success = !cacheDate
- stationData = response
- onStationData(response)
+ stationData.arrivals = responses[0].arrivals
+ stationData.departures = responses[1].departures
+ onStationData(stationData)
// Check in 1 seconds if network error
reschedule(success ? undefined : 1000)
return success
@@ -185,7 +228,7 @@ function rsk() {
}
window.addEventListener('load', function (e) {
- if (!new URL(window.location.href).searchParams.has('station')) {
+ if (!new URL(window.location.href).searchParams.has('stationId')) {
window.history.back()
this.setTimeout(function () {
var url = new URL(window.location.href)
@@ -196,7 +239,7 @@ window.addEventListener('load', function (e) {
var sp = new URL(window.location.href).searchParams
- station = sp.get('station')
+ stationId = sp.get('stationId')
date = sp.has('date') ? new Date(sp.get('date')) : new Date()
// View departures first
diff --git a/view-train.css b/view-train.css
index 6fc7fe8..6f35243 100644
--- a/view-train.css
+++ b/view-train.css
@@ -45,11 +45,29 @@
background-color: #fafafa;
}
+.stationItem.cancelled:nth-of-type(odd) {
+ background-color: #fffafa;
+}
+
+.stationItem.cancelled:nth-of-type(even) {
+ background-color: #ffeaea;
+}
+
.stationItem .name {
text-align: center;
grid-area: name;
}
+.stationItem.not-in-journey .name, .stationItem.not-in-journey .arrival, .stationItem.not-in-journey .departure {
+ color: grey;
+}
+
+.stationItem.cancelled .name {
+ text-decoration: line-through;
+ text-decoration-color: red;
+ color: red;
+}
+
.stationItem .arrival {
text-align: start;
grid-area: arr;
@@ -90,6 +108,10 @@
grid-area: platform;
}
+.stationItem .platform.changed {
+ color: red;
+}
+
.stationItem .notes {
grid-area: notes;
}
@@ -98,8 +120,36 @@
text-align: center;
}
+.remarkItem {
+ margin-top: 4px;
+ margin-bottom: 4px;
+}
+
+.remarkItem:nth-of-type(even) {
+ background-color: #fafafa;
+}
+
.last-refreshed {
font-size: 12px;
text-transform: none;
}
+.remark-status, .remark-status * {
+ color: red !important;
+}
+
+.remark-board, .remark-exit {
+ color: blue !important;
+}
+
+#actual-map {
+ height: 500px;
+}
+
+#actual-map a {
+ display: initial;
+ padding: initial;
+ color: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+}
diff --git a/view-train.dark.css b/view-train.dark.css
new file mode 100644
index 0000000..e14466c
--- /dev/null
+++ b/view-train.dark.css
@@ -0,0 +1,49 @@
+@media(prefers-color-scheme: dark) {
+
+ .early {
+ color: lightgreen;
+ }
+
+ .late {
+ color: #ff3333;
+ }
+
+ .station {
+ color: white;
+ }
+
+ .stationItem:nth-of-type(even) {
+ background-color: #0f0f0f;
+ }
+
+ .stationItem .arrival .original, .stationItem .departure .original {
+ color: #afafaf;
+ }
+
+ .remark-status, .remark-status * {
+ color: #ff3333 !important;
+ }
+
+ .remark-board, .remark-exit {
+ color: #8888ff !important;
+ }
+
+ .stationItem.not-in-journey .name, .stationItem.not-in-journey .arrival, .stationItem.not-in-journey .departure {
+ color: #aaaaaa;
+ }
+
+ .stationItem.cancelled .name {
+ text-decoration: line-through;
+ text-decoration-color: #ff3333;
+ color: #ff3333;
+ }
+
+ .stationItem .platform.changed {
+ color: #ff3333;
+ }
+
+ .remarkItem:nth-of-type(even) {
+ background-color: #202020;
+ }
+
+}
diff --git a/view-train.html b/view-train.html
index 7311146..70986d6 100644
--- a/view-train.html
+++ b/view-train.html
@@ -4,13 +4,21 @@
-
View Train - InfoTren
+
View Train - InfoDTrain
+
+
+
+
@@ -56,6 +64,10 @@
Stations
+