ctucx.git: trainsearch

web based trip-planner, fork of https://cyberchaos.dev/yuka/trainsearch

commit 24c289fa4af4f49b3c4cd51c24c77dcbc3056c5b
parent f95b7e361263aeea1b11458e42893496da101ba5
Author: Yureka <yuka@yuka.dev>
Date: Sat, 10 Sep 2022 18:34:48 +0200

departuresView
6 files changed, 200 insertions(+), 80 deletions(-)
M
src/app_functions.js
|
54
+++++++++++++++++++++++++++++++++++++++++++++++++++---
A
src/departuresView.js
|
119
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
src/journeyView.js
|
24
+++---------------------
M
src/main.js
|
9
++++++++-
M
src/tripView.js
|
50
+++++---------------------------------------------
M
static/style.css
|
24
++++++++++++++----------
diff --git a/src/app_functions.js b/src/app_functions.js
@@ -1,4 +1,5 @@
 import { db } from './dataStorage.js';
+import { go } from './router.js';
 import { settings, subscribeSettings } from './settings.js';
 import { showLoader, hideOverlay, showModal, showAlertModal } from './overlays.js';
 import { languages } from './languages.js';

@@ -67,13 +68,17 @@ const processJourneys = data => {
 export const processLeg = leg => {
 	if (leg.plannedDeparture) leg.plannedDeparture = new Date(leg.plannedDeparture);
 	if (leg.plannedArrival) leg.plannedArrival = new Date(leg.plannedArrival);
+	if (leg.plannedWhen) leg.plannedWhen = new Date(leg.plannedWhen);
 	if (leg.departure) leg.departure = new Date(leg.departure);
 	if (leg.arrival) leg.arrival = new Date(leg.arrival);
+	if (leg.when) leg.when = new Date(leg.when);
 	for (const stopover of (leg.stopovers || [])) {
 		if (stopover.plannedDeparture) stopover.plannedDeparture = new Date(stopover.plannedDeparture);
 		if (stopover.plannedArrival) stopover.plannedArrival = new Date(stopover.plannedArrival);
+		if (stopover.plannedWhen) stopover.plannedWhen = new Date(stopover.plannedWhen);
 		if (stopover.departure) stopover.departure = new Date(stopover.departure);
 		if (stopover.arrival) stopover.arrival = new Date(stopover.arrival);
+		if (stopover.when) stopover.when = new Date(stopover.when);
 	}
 };
 const processJourney = journey => {

@@ -205,7 +210,7 @@ export const parseName = (point) => {
 	return nameHTML;
 };
 
-export const ds100Names = (id) => {
+const ds100Names = (id) => {
 	if (!settings.showRIL100Names) return '';
 	if (!ds100[Number(id)]) return '';
 	return '('+ds100[Number(id)]+')';

@@ -223,10 +228,26 @@ export const loadDS100 = async () => {
 };
 
 export const timeTemplate = (data, mode) => {
-	const time = data[mode] || data['planned'+mode.replace(/^\w/, c => c.toUpperCase())];
+	const fieldsMap = {
+		when: {
+			departure: 'departure',
+			arrival: 'arrival',
+		},
+		plannedWhen: {
+			departure: 'plannedDeparture',
+			arrival: 'plannedArrival',
+		},
+		delay: {
+			departure: 'departureDelay',
+			arrival: 'arrivalDelay',
+		},
+	};
+	const getField = fieldName => data[fieldsMap[fieldName][mode] || fieldName];
+
+	const time = getField('when') || getField('plannedWhen');
 	if (!time) return '-';
 
-	const delayMinutes = Math.round(data[mode+'Delay'] / 60);
+	const delayMinutes = Math.round(getField('delay') / 60);
 
 	return html`
 		${delayMinutes != 0 ? html`

@@ -237,3 +258,30 @@ export const timeTemplate = (data, mode) => {
 	`;
 };
 
+export const platformTemplate = (data) => {
+	if (data.departurePlatform) {
+		if (data.departurePlatform != data.plannedDeparturePlatform) {
+			return html`<b>${data.departurePlatform}</b>`;
+		} else {
+			return data.plannedDeparturePlatform;
+		}
+	} else if (data.platform) {
+		if (data.platform != data.plannedPlatform) {
+			return html`<b>${data.platform}</b>`;
+		} else {
+			return data.plannedPlatform;
+		}
+	} else if (data.arrivalPlatform) {
+		if (data.arrivalPlatform != data.plannedArrivalPlatform) {
+			return html`<b>${data.arrivalPlatform}</b>`;
+		} else {
+			return data.plannedArrivalPlatform;
+		}
+	} else {
+		return '-';
+	}
+};
+
+export const stopTemplate = (profile, stop) => {
+	return html`<a href="#/d/${profile}/${stop.id}">${stop.name} ${ds100Names(stop.id)}</a>`;
+}
diff --git a/src/departuresView.js b/src/departuresView.js
@@ -0,0 +1,119 @@
+import { settings } from './settings.js';
+import { showDiv, hideDiv, ElementById, formatDateTime, formatDuration, formatPrice } from './helpers.js';
+import { ConsoleLog, parseName, t, timeTemplate, processLeg, platformTemplate } from './app_functions.js';
+import { showAlertModal, showLoader, hideOverlay, showModal } from './overlays.js';
+import { go } from './router.js';
+import { html, render } from 'lit-html';
+import { getHafasClient, client } from './hafas_client';
+
+const remarksModalTemplate = (type, remarks) => html`
+	<table class="remarks">
+		${remarks.map(element => html`
+			<tr>
+			<td>
+				<span class="remark icon-${type}"></span>
+				<span>${element.text}</span>
+			</td>
+			</tr>
+		`)}
+	</table>
+`;
+
+const getAdditionalName = (line) => {
+	const splitName = line.name.split(' ');
+	if (splitName.length === 2 && line.fahrtNr && line.fahrtNr != splitName[1])
+		return `${splitName[0]} ${line.fahrtNr}`;
+	else
+		return null;
+};
+
+const departuresTemplate = (data, profile) => {
+	let changes = 0;
+	let lastArrival;
+
+	//<a class="reload icon-reload" title="${t("reload")}" @click=${() => refreshJourneyView(data.refreshToken, profile)}>${t("reload")}</a>
+	return html`
+		<div class="departures">
+			<header>
+				<a class="back icon-back invisible"></a>
+				<div class="header-content">
+					<h3>Departures from ${data.name}</h3>
+				</div>
+				<a class="reload icon-reload invisible">${t("reload")}</a>
+			</header>
+
+			<div class="card">
+			<table>
+				<thead>
+					<tr>
+						<th>Time</th>
+						<th class="station-column"></th>
+						<th>${t('platform')}</th>
+					</tr>
+				</thead>
+				<tbody>
+					${(data.departures || []).map(departure => html`
+						<tr class="departure" @click=${() => go(`/t/${profile}/${departure.tripId}`)}>
+							<td class="${departure.cancelled ? 'cancelled' : ''}"><span>${timeTemplate(departure)}</span></td>
+							<td class="${departure.cancelled ? 'cancelled' : ''}"><span>${departure.line.name}${departure.direction ? html` → ${departure.direction}` : ''}</span></td>
+							${departure.cancelled ? html`
+								<td><span class="cancelled-text">${t('cancelled-ride')}</span></td>
+							` : html`
+								<td><span>${platformTemplate(departure)}</span></td>
+							`}
+						</tr>
+					`)}
+				</tbody>
+			</table>
+			</div>
+		</div>
+	`;
+};
+
+export const departuresView = async (match, isUpdate) => {
+	if (!isUpdate) showLoader();
+	let profile, stopId, when, data;
+	try {
+		profile = match[0];
+		stopId = match[1];
+		if (match[2]) when = new Date(parseInt(match[2].substring(1)));
+		const client = await getHafasClient(profile);
+		const [ departures, stopInfo ] = await Promise.all([
+			client.departures(stopId, { when }),
+			client.stop(stopId),
+		]);
+		for (let departure of departures) {
+			processLeg(departure);
+		};
+		data = { ...stopInfo, departures };
+	} catch(e) {
+		showAlertModal(e.toString());
+		throw e;
+	}
+	hideOverlay();
+
+	ConsoleLog(data);
+	render(departuresTemplate(data, profile), ElementById('content'));
+
+	//if (!isUpdate) refreshJourneyView(refreshToken); // update data in the background
+
+	/*const history_id = dataStorage.journeysHistory.findIndex(obj => obj.reqId === reqId);
+
+	if (dataStorage.journeysHistory[history_id] !== undefined) {
+		dataStorage.journeysHistory[history_id].journeyId = journeyId;
+		saveDataStorage();
+	}*/
+};
+
+const refreshJourneyView = async (refreshToken, profile) => {
+	document.querySelector('.reload').classList.add('spinning');
+	try {
+		await refreshJourney(refreshToken, profile);
+	} catch(e) {
+		showAlertModal(e.toString());
+		document.querySelector('.reload').classList.remove('spinning');
+		throw e;
+	}
+	journeyView([profile, refreshToken], true);
+	document.querySelector('.reload').classList.remove('spinning');
+};
diff --git a/src/journeyView.js b/src/journeyView.js
@@ -1,6 +1,6 @@
 import { settings } from './settings.js';
 import { showDiv, hideDiv, ElementById, formatDateTime, formatDuration, formatPrice } from './helpers.js';
-import { ConsoleLog, parseName, ds100Names, t, timeTemplate, getJourney, refreshJourney } from './app_functions.js';
+import { ConsoleLog, parseName, t, timeTemplate, getJourney, refreshJourney, platformTemplate, stopTemplate } from './app_functions.js';
 import { showAlertModal, showLoader, hideOverlay, showModal } from './overlays.js';
 import { go } from './router.js';
 import { html, render } from 'lit-html';

@@ -107,8 +107,8 @@ const legTemplate = (leg, profile) => {
 						<tr class="stop ${stop.cancelled ? 'cancelled' : ''}">
 							<td><span>${timeTemplate(stop, 'arrival')}</span></td>
 							<td><span>${timeTemplate(stop, 'departure')}</span></td>
-							<td><span>${stop.stop.name} ${ds100Names(stop.stop.id)}</span></td>
-							<td><span>${stopPlatformTemplate(stop)}</span></td>
+							<td>${stopTemplate(profile, stop.stop)}</td>
+							<td><span>${platformTemplate(stop)}</span></td>
 						</tr>
 					`)}
 				</tbody>

@@ -168,24 +168,6 @@ const journeyTemplate = (data, profile) => {
 	`;
 };
 
-const stopPlatformTemplate = (data) => {
-	if (data.departurePlatform) {
-		if (data.departurePlatform != data.plannedDeparturePlatform) {
-			return html`<b>${data.departurePlatform}</b>`;
-		} else {
-			return data.plannedDeparturePlatform;
-		}
-	} else if (data.arrivalPlatform) {
-		if (data.arrivalPlatform != data.plannedArrivalPlatform) {
-			return html`<b>${data.arrivalPlatform}</b>`;
-		} else {
-			return data.plannedArrivalPlatform;
-		}
-	} else {
-		return '-';
-	}
-};
-
 export const journeyView = async (match, isUpdate) => {
 	if (!isUpdate) showLoader();
 	let profile, refreshToken, data;
diff --git a/src/main.js b/src/main.js
@@ -3,10 +3,12 @@ import { searchView } from './searchView.js';
 import { journeysView } from './journeysView.js';
 import { journeyView } from './journeyView.js';
 import { tripView } from './tripView.js';
+import { departuresView } from './departuresView.js';
 import { initSettings, settings } from './settings.js';
 import { initDataStorage } from './dataStorage.js';
 import { initHafasClient } from './hafas_client';
-import { showDiv, hideDiv, ElementById } from './helpers.js';
+import { hideDiv, ElementById } from './helpers.js';
+import { showAlertModal } from './overlays.js';
 
 (async () => {
 	// read settings from indexeddb

@@ -18,6 +20,11 @@ import { showDiv, hideDiv, ElementById } from './helpers.js';
 	route(/^\/([a-zA-Z0-9]+)\/([a-z]+)$/, journeysView);
 	route(/^\/j\/([a-z]+)\/(.+)$/, journeyView);
 	route(/^\/t\/([a-z]+)\/(.+)$/, tripView);
+	route(/^\/d\/([a-z]+)\/([^/]+)(\/[0-9]+)?$/, departuresView);
+	route(/^.*$/, async () => {
+		await showAlertModal('Route not found');
+		go('/');
+	});
 
 	ElementById('overlay').innerHTML = '';
 	hideDiv('overlay');
diff --git a/src/tripView.js b/src/tripView.js
@@ -1,6 +1,6 @@
 import { settings } from './settings.js';
 import { showDiv, hideDiv, ElementById, formatDateTime, formatDuration, formatPrice } from './helpers.js';
-import { ConsoleLog, parseName, ds100Names, t, timeTemplate, processLeg } from './app_functions.js';
+import { ConsoleLog, parseName, t, timeTemplate, processLeg, platformTemplate, stopTemplate } from './app_functions.js';
 import { showAlertModal, showLoader, hideOverlay, showModal } from './overlays.js';
 import { go } from './router.js';
 import { html, render } from 'lit-html';

@@ -66,9 +66,9 @@ const tripTemplate = (data, profile) => {
 			<header>
 				<a class="back icon-back invisible"></a>
 				<div class="header-content">
-					<h3>wip</h3>
+					<h3>Trip of ${data.line.name}</h3>
 				</div>
-				<a class="reload icon-reload" title="${t("reload")}" @click=${() => refreshJourneyView(data.refreshToken, profile)}>${t("reload")}</a>
+				<a class="reload icon-reload invisible">${t("reload")}</a>
 			</header>
 
 			<div class="card">

@@ -122,8 +122,8 @@ const tripTemplate = (data, profile) => {
 						<tr class="stop ${stop.cancelled ? 'cancelled' : ''}">
 							<td><span>${timeTemplate(stop, 'arrival')}</span></td>
 							<td><span>${timeTemplate(stop, 'departure')}</span></td>
-							<td><span>${stop.stop.name} ${ds100Names(stop.stop.id)}</span></td>
-							<td><span>${stopPlatformTemplate(stop)}</span></td>
+							<td><span>${stopTemplate(profile, stop.stop)}</span></td>
+							<td><span>${platformTemplate(stop)}</span></td>
 						</tr>
 					`)}
 				</tbody>

@@ -133,24 +133,6 @@ const tripTemplate = (data, profile) => {
 	`;
 };
 
-const stopPlatformTemplate = (data) => {
-	if (data.departurePlatform) {
-		if (data.departurePlatform != data.plannedDeparturePlatform) {
-			return html`<b>${data.departurePlatform}</b>`;
-		} else {
-			return data.plannedDeparturePlatform;
-		}
-	} else if (data.arrivalPlatform) {
-		if (data.arrivalPlatform != data.plannedArrivalPlatform) {
-			return html`<b>${data.arrivalPlatform}</b>`;
-		} else {
-			return data.plannedArrivalPlatform;
-		}
-	} else {
-		return '-';
-	}
-};
-
 export const tripView = async (match, isUpdate) => {
 	if (!isUpdate) showLoader();
 	let profile, refreshToken, data;

@@ -168,26 +150,4 @@ export const tripView = async (match, isUpdate) => {
 
 	ConsoleLog(data);
 	render(tripTemplate(data, profile), ElementById('content'));
-
-	//if (!isUpdate) refreshJourneyView(refreshToken); // update data in the background
-
-	/*const history_id = dataStorage.journeysHistory.findIndex(obj => obj.reqId === reqId);
-
-	if (dataStorage.journeysHistory[history_id] !== undefined) {
-		dataStorage.journeysHistory[history_id].journeyId = journeyId;
-		saveDataStorage();
-	}*/
-};
-
-const refreshJourneyView = async (refreshToken, profile) => {
-	document.querySelector('.reload').classList.add('spinning');
-	try {
-		await refreshJourney(refreshToken, profile);
-	} catch(e) {
-		showAlertModal(e.toString());
-		document.querySelector('.reload').classList.remove('spinning');
-		throw e;
-	}
-	journeyView([profile, refreshToken], true);
-	document.querySelector('.reload').classList.remove('spinning');
 };
diff --git a/static/style.css b/static/style.css
@@ -56,14 +56,14 @@ header {
 }
 .cancelled-text {
 	font-weight: bold;
-	color: red;
+	color: red !important;
 }
 
 .pointer {
 	cursor: pointer;
 }
 
-.back.invisible {
+.invisible {
 	visibility: hidden;
 }
 .back,

@@ -238,11 +238,16 @@ tbody tr:hover td {
 }
 
 .journey,
-.journeys {
+.journeys,
+.departures {
 	display: flex;
 	flex-direction: column;
 	min-height: 100vh;
 }
+.journeys tbody tr,
+.departures tbody tr {
+	cursor: pointer;
+}
 
 .search {
 	color: white;

@@ -312,7 +317,8 @@ tbody tr:hover td {
 }
 
 .journeys table a,
-.journey table span {
+.journey table span,
+.departures table span {
 	padding: 5px 3px;
 	display: flex;
 	justify-content: center;

@@ -322,7 +328,8 @@ tbody tr:hover td {
 	color: black;
 }
 
-.journey table a {
+.journey table a,
+.departures table a {
 	padding: 5px 3px;
 	display: flex;
 	justify-content: center;

@@ -336,8 +343,8 @@ tbody tr:hover td {
 	padding: 0;
 }
 
-.journey tbody td:nth-child(3) {
-	text-align: left;
+.departures tbody td:nth-child(2) span {
+        justify-content: start;
 }
 
 .journey tbody:not(:last-child) {

@@ -410,9 +417,6 @@ tbody tr:hover td {
 
 
 @media (max-width: 799px) {
-	.back.invisible {
-		display: none;
-	}
 	.header-content {
 		flex-grow: 1;
 	}