katja's git: oeffisearch

fast and simple tripplanner

commit aaf74e860006b10359634644961c518ee4070d94
parent 1ccd8603fe332643371f4d49cd987e2e8c7d3609
Author: Katja (ctucx) <git@ctu.cx>
Date: Sun, 20 Apr 2025 19:07:58 +0200

improve state handling
8 files changed, 124 insertions(+), 103 deletions(-)
M
src/baseView.js
|
13
+++++++++++--
M
src/departuresView.js
|
25
+++++++++++--------------
M
src/journeyView.js
|
32
+++++++++++++++++++++-----------
M
src/journeysCanvas.js
|
12
+++++++-----
M
src/journeysView.js
|
59
++++++++++++++++++++++++++++++++---------------------------
M
src/searchView.js
|
55
++++++++++++++++++++++++++++---------------------------
M
src/settingsView.js
|
2
+-
M
src/tripView.js
|
29
+++++++++++++----------------
diff --git a/src/baseView.js b/src/baseView.js
@@ -36,26 +36,34 @@ export class BaseView extends LitElement {
 
 	connectedCallback () {
 		super.connectedCallback();
+
 		window.addEventListener('online',  this.connectionHandler);
 		window.addEventListener('offline', this.connectionHandler);
 	}
 
 	disconnectedCallback () {
 		super.disconnectedCallback();
+
 		window.removeEventListener('online',  this.connectionHandler);
 		window.removeEventListener('offline', this.connectionHandler);
 	}
 
+	updated () {
+		if (isDevServer) console.info('BaseView(overlayState):', this.overlayState);
+	}
+
 	connectionHandler = event => { this.isOffline = event.type === 'offline'; };
 
 	showLoaderOverlay = ()            => this.showOverlay('loader');
 	showDialogOverlay = (title, body) => this.showOverlay('dialog', body, title);
 	showAlertOverlay  = text          => this.showOverlay('alert', text);
 	showSelectOverlay = items         => this.showOverlay('select', items);
+
 	hideOverlay = () => {
 		this.overlayState.visible = false;
 		this.requestUpdate();
 	};
+
 	showOverlay = (type, content, title) => {
 		this.overlayState = {
 			type, content, title,

@@ -71,7 +79,7 @@ export class BaseView extends LitElement {
 		}
 	};
 
-	render () {
+	render = () => {
 		let overlayContent;
 
 		if (this.overlayState.visible) {

@@ -105,5 +113,6 @@ export class BaseView extends LitElement {
 			this.renderView(),
 			!this.overlayState.visible ? nothing : html`<div class="overlay flex-center" @click=${this.overlayHandler}>${overlayContent}</div>`
 		];
-	}
+	};
+
 }
diff --git a/src/departuresView.js b/src/departuresView.js
@@ -32,28 +32,24 @@ class DeparturesView extends BaseView {
 		this.viewState  = null;
 	}
 
-	async connectedCallback () {
-		super.connectedCallback();
-		await sleep(100);
-
-		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
-
-		if (!this.isOffline) await this.updateViewState();
-	}
-
 	disconnectedCallback () {
 		super.disconnectedCallback();
 
 		this.viewState = null;
 	}
 
+	async willUpdate (previous) {
+		if (previous.has('stopId')) this.viewState = null;
 
-	async updated (previous) {
-		super.updated(previous);
+		if (!this.viewState) await this.updateViewState();
+	}
+
+	updated (previous) {
+		super.updated();
 
-		if (isDevServer) console.info('updated(): ', previous);
+		if (isDevServer) console.info('DeparturesView(updated):', previous);
 
-		if (previous.has('isOffline') && this.viewState === null) await this.updateViewState();
+		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
 	}
 
 	updateViewState = async () => {

@@ -76,7 +72,7 @@ class DeparturesView extends BaseView {
 				departures
 			};
 
-			if (isDevServer) console.info('viewState:', this.viewState);
+			if (isDevServer) console.info('DeparturesView(viewState):', this.viewState);
 		} catch(e) {
 			this.showAlertOverlay(e.toString());
 			console.error(e);

@@ -144,6 +140,7 @@ class DeparturesView extends BaseView {
 			`
 		)
 	];
+
 }
 
 customElements.define('departures-view', DeparturesView);
diff --git a/src/journeyView.js b/src/journeyView.js
@@ -37,13 +37,8 @@ class JourneyView extends BaseView {
 		this.settingsState = settings.getState();
 	}
 
-	async connectedCallback () {
+	connectedCallback () {
 		super.connectedCallback();
-		await sleep(100);
-
-		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
-
-		await this.updateViewState();
 
 		this._unsubscribeSettingsState = settings.subscribe(state => {
 			this.settingsState = state;

@@ -61,6 +56,20 @@ class JourneyView extends BaseView {
 		}
 	}
 
+	async willUpdate (previous) {
+		if (previous.has('refreshToken')) this.viewState = null;
+
+		if (!this.viewState) await this.updateViewState();
+	}
+
+	updated (previous) {
+		super.updated();
+
+		if (isDevServer) console.info('JourneyView(updated):', previous);
+
+		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
+	}
+
 	renderView = () => [
 		html`
 			<div class="header-container">

@@ -173,7 +182,7 @@ class JourneyView extends BaseView {
 				</div>
 			`;
 		}
-	}
+	};
 
 	updateViewState = async viewState => {
 		this.isUpdating = true;

@@ -246,13 +255,13 @@ class JourneyView extends BaseView {
 				}
 			}
 
-			if (isDevServer) console.info('viewState:', this.viewState);
+			if (isDevServer) console.info('JourneyView(viewState):', this.viewState);
 		} catch(e) {
 			this.showAlertOverlay(e.toString());
 			console.error(e);
 		}
 		this.isUpdating = false;
-	}
+	};
 
 	refreshJourney = async () => {
 		if (this.isOffline !== false) return;

@@ -267,7 +276,7 @@ class JourneyView extends BaseView {
 			console.error(e);
 		}
 		this.isUpdating = false;
-	}
+	};
 
 	moreModal = async () => {
 		const options = [

@@ -288,7 +297,7 @@ class JourneyView extends BaseView {
 			await navigator.clipboard.writeText(window.location);
 			this.showAlertOverlay('URL has been copied to clipboard.');
 		}
-	}
+	};
 
 	ticketsAction = async () => {
 		try {

@@ -367,6 +376,7 @@ class JourneyView extends BaseView {
 
 		window.location = URL.createObjectURL(file);
 	};
+
 }
 
 customElements.define('journey-view', JourneyView);
diff --git a/src/journeysCanvas.js b/src/journeysCanvas.js
@@ -71,8 +71,7 @@ export class JourneysCanvas extends BaseView {
 	}
 
 	updated (previous) {
-		super.updated(previous);
-		if (isDevServer) console.info('canvasState: ', this.canvasState);
+		if (isDevServer) console.info('JourneysCanvas(canvasState):', this.canvasState);
 	}
 
 	resetCanvasPosition = () => {

@@ -82,6 +81,8 @@ export class JourneysCanvas extends BaseView {
 	getCanvas = () => html`<canvas @mousedown=${this.mouseDownHandler} @touchstart=${this.mouseDownHandler} ${ref(this.canvasRef)}></canvas>`;
 
 	connectCanvas = () => {
+		this.resizeHandler();
+
 		if (!this.canvasState.handlersConnected) {
 			window.addEventListener('mouseup',   this.mouseUpHandler);
 			window.addEventListener('touchend',  this.mouseUpHandler);

@@ -91,7 +92,6 @@ export class JourneysCanvas extends BaseView {
 			window.addEventListener('zoom',      this.resizeHandler);
 			this.canvasState.handlersConnected = true;
 		}
-		this.resizeHandler();
 	};
 
 	disconnectCanvas = () => {

@@ -107,7 +107,7 @@ export class JourneysCanvas extends BaseView {
 	resizeHandler = () => {
 		const canvasElement = this.canvasRef.value;
 
-		if (canvasElement === undefined) return true;
+		if (!canvasElement) return true;
 
 		const canvasContext = canvasElement.getContext('2d');
 

@@ -222,6 +222,7 @@ export class JourneysCanvas extends BaseView {
 		if (this.isOffline !== false) return;
 		if (!['db', 'rmv'].includes(this.viewState.profile)) return;
 
+		if (isDevServer) console.info('JourneysCanvas(getCoachSequences): fetch start');
 		this.isUpdating = true;
 		for (const journey of this.viewState.journeys) {
 			for (const leg of journey.legs) {

@@ -236,6 +237,7 @@ export class JourneysCanvas extends BaseView {
 			}
 		}
 		this.isUpdating = false;
+		if (isDevServer) console.info('JourneysCanvas(getCoachSequences): fetch end');
 	};
 
 	getTrainTypeTexts = leg => {

@@ -250,7 +252,7 @@ export class JourneysCanvas extends BaseView {
 
 	renderCanvas = () => {
 		const canvasElement = this.canvasRef.value;
-		if (canvasElement === undefined) return true;
+		if (!canvasElement) return true;
 
 		const canvasContext = canvasElement.getContext('2d');
 
diff --git a/src/journeysView.js b/src/journeysView.js
@@ -37,13 +37,9 @@ export class JourneysView extends JourneysCanvas {
 		this.settingsState = settings.getState();
 	}
 
-	async connectedCallback() {
+	connectedCallback () {
 		super.connectedCallback();
-		await sleep(100);
 
-		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
-
-		if (this.viewState === null) await this.updateViewState();
 		if (this.mode === 'canvas') this.connectCanvas();
 
 		this._unsubscribeSettingsState = settings.subscribe(state => {

@@ -64,31 +60,36 @@ export class JourneysView extends JourneysCanvas {
 		}
 	}
 
-	async updated (previous) {
-		await super.updated(previous);
-		if (isDevServer) console.info('updated(): ', previous);
+	async willUpdate (previous) {
+		if (previous.has('slug')) this.viewState = null;
 
-		if (previous.has('mode') && this.settingsState.journeysViewMode !== this.mode) this.settingsState.setJourneysViewMode(this.mode);
+		if (previous.has('mode')) {
+			if (previous.get('mode') === 'canvas' && this.mode !== 'canvas') this.disconnectCanvas();
+			if (this.settingsState.journeysViewMode !== this.mode)           this.settingsState.setJourneysViewMode(this.mode);
+		}
 
-		if (previous.has('slug')) {
+		if (!this.viewState) {
 			this.resetCanvasPosition();
 			await this.updateViewState();
 		}
 
-		if (previous.has('mode')) {
-			if (this.mode === 'canvas') this.connectCanvas();
-			if (previous.get('mode') === 'canvas' && this.mode !== 'canvas') this.disconnectCanvas();
-		}
-
 		if (this.mode === 'canvas') {
 			this.connectCanvas();
-			if (previous.has('viewState') && this.viewState !== null) {
-				this.renderCanvas();
-				if (previous.get('viewState') !== undefined) await this.getCoachSequences();
+
+			if (previous.has('viewState') && this.viewState) {
+				if (previous.get('viewState') !== null) await this.getCoachSequences();
 			}
 		}
 	}
 
+	updated (previous) {
+		super.updated();
+
+		if (isDevServer) console.info('JourneysView(updated):', previous);
+
+		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
+	}
+
 	renderView = () => [
 		html`
 			<div class="header-container">

@@ -110,7 +111,8 @@ export class JourneysView extends JourneysCanvas {
 							</a>
 						</div>
 					</div>
-					<a class="icon-reload ${classMap({ spinning: this.isUpdating, invisible: this.isOffline })}" title=${t("refresh")} @click=${this.refreshJourneys}></a>
+					<a class="icon-reload ${classMap({ spinning: this.isUpdating, invisible: this.isOffline })}" tabindex=0 title=${t("refresh")}
+					@keydown=${this.keyClickHandler} @click=${this.refreshJourneys}></a>
 				</header>
 			</div>
 		`,

@@ -123,7 +125,8 @@ export class JourneysView extends JourneysCanvas {
 					<div class="container">
 						${when(
 							this.viewState.earlierRef,
-							() => html`<a class="arrowButton icon-arrow flipped flex-center" title="${t('earlier')}" @click=${() => this.moreJourneys('earlier')}></a>`
+							() => html`<div tabindex=0 class="arrowButton icon-arrow flipped flex-center" title="${t('earlier')}"
+							@keydown=${this.keyClickHandler} @click=${() => this.moreJourneys('earlier')}></a>`
 						)}
 
 						<div class="table" style="grid-template-columns: repeat(${this.settingsState.showPrices && this.viewState.profile === 'db' ? '6' : '5'}, 1fr) .3fr">

@@ -144,7 +147,8 @@ export class JourneysView extends JourneysCanvas {
 
 						${when(
 							this.viewState.laterRef,
-							() => html`<a class="arrowButton icon-arrow flex-center" title="${t('later')}" @click=${() => this.moreJourneys('later')}></a>`
+							() => html`<a tabindex=0 class="arrowButton icon-arrow flex-center" title="${t('later')}"
+							@keydown=${this.keyClickHandler} @click=${() => this.moreJourneys('later')}></a>`
 						)}
 					</div>
 					<footer-component></footer-component>

@@ -159,7 +163,7 @@ export class JourneysView extends JourneysCanvas {
 
 		return html`
 			<a class="row" href="#/j/${this.viewState.profile}/${journey.refreshToken}">
-				<span class=${!journey.cancelled ? nothing : 'cancelled'}>${timeTemplate(firstLeg, 'departure')}</span>
+				<span class=${classMap({ cancelled: journey.cancelled })}>${timeTemplate(firstLeg, 'departure')}</span>
 				${when(
 					!journey.cancelled,
 					() => html`<span>${timeTemplate(lastLeg, 'arrival')}</span>`,

@@ -178,7 +182,7 @@ export class JourneysView extends JourneysCanvas {
 				<span><i class="icon-arrow" style="transform:rotate(-90deg)"></i></span>
 			</a>
 		`;
-	}
+	};
 
 	updateViewState = async () => {
 		try {

@@ -221,12 +225,12 @@ export class JourneysView extends JourneysCanvas {
 			});
 
 			this.viewState = viewState;
-			if (isDevServer) console.info('viewState: ', this.viewState);
+			if (isDevServer) console.info('JourneysView(viewState):', this.viewState);
 		} catch(e) {
 			this.showAlertOverlay(e.toString());
 			console.error(e);
 		}
-	}
+	};
 
 	refreshJourneys = async () => {
 		if (this.isOffline || this.isUpdating) return;

@@ -242,7 +246,7 @@ export class JourneysView extends JourneysCanvas {
 			this.showAlertOverlay(e.toString());
 			console.error(e);
 		}
-	}
+	};
 
 	moreJourneys = async mode => {
 		if (this.isOffline) {

@@ -263,7 +267,8 @@ export class JourneysView extends JourneysCanvas {
 			console.error(e);
 		}
 		this.hideOverlay();
-	}
+	};
+
 }
 
 customElements.define('journeys-view', JourneysView);
diff --git a/src/searchView.js b/src/searchView.js
@@ -66,11 +66,8 @@ class SearchView extends BaseView {
 		};
 	}
 
-	async connectedCallback() {
+	async connectedCallback () {
 		super.connectedCallback();
-		await sleep(100);
-
-		setThemeColor(queryBackgroundColor(document, 'body'));
 
 		this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse();
 

@@ -92,18 +89,21 @@ class SearchView extends BaseView {
 		}
 	}
 
-	async updated (previous) {
-		super.updated(previous);
-
-		if (isDevServer) console.info('updated():', previous);
-
+	async willUpdate (previous) {
 		if (
 			previous.has('settingsState') &&
 			previous.get('settingsState') !== undefined &&
 			previous.get('settingsState').profile !== this.settingsState.profile
 		) await this.profileChangeHandler();
+	}
 
-		if (isDevServer) console.info('settingsState:', this.settingsState);
+	updated (previous) {
+		super.updated();
+
+		if (isDevServer) console.info('SearchView(updated):', previous);
+		if (isDevServer) console.info('SearchView(settingsState):', this.settingsState);
+
+		setThemeColor(queryBackgroundColor(document, 'body'));
 	}
 
 	renderView = () => {

@@ -225,17 +225,17 @@ class SearchView extends BaseView {
 				<footer-component></footer-component>
 			</div>
 	    `;
-	}
+	};
 
 	swapFromTo = () => {
 		this.location.from = [this.location.to, this.location.to = this.location.from][0];
 		this.requestUpdate();
-	}
+	};
 
 	resetDate = () => {
 		this.date = new CustomDate(); 
 		this.requestUpdate();
-	}
+	};
 
 	showSettings  = () => this.showDialogOverlay('settings', html`<settings-view></settings-view>`);
 	toggleHistory = () => this.showHistory = !this.showHistory;

@@ -256,7 +256,7 @@ class SearchView extends BaseView {
 		}
 
 		this.showSelectOverlay(options);
-	}
+	};
 
 	 setFromHistory = async id => {
 		const entry = await db.getHistoryEntry(id);

@@ -271,7 +271,7 @@ class SearchView extends BaseView {
 		});
 
 		this.requestUpdate();
-	}
+	};
 
 	iconForProduct = id => {
 		const productIcons = {

@@ -309,7 +309,7 @@ class SearchView extends BaseView {
 		};
 
 		return productIcons[id] || `icon-${id}`;
-	}
+	};
 
 	focusNextElement = currentElementId => {
 		switch (currentElementId) {

@@ -327,7 +327,7 @@ class SearchView extends BaseView {
 				this.renderRoot.querySelector('[type=submit]').focus();
 				break;
 		}
-	}
+	};
 
 	setSuggestion = (name, num, pointerType) => {
 		this.location[name].value              = formatPoint(this.location[name].suggestions[num]);

@@ -335,7 +335,7 @@ class SearchView extends BaseView {
 		this.location[name].suggestionsVisible = false;
 		this.requestUpdate();
 		if (pointerType !== '') this.focusNextElement(name);
-	}
+	};
 
 	profileChangeHandler = async () => {
 		[ 'from', 'via','to' ].forEach(name => {

@@ -349,7 +349,7 @@ class SearchView extends BaseView {
 		});
 
 		this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse();
-	}
+	};
 
 	submitHandler = async event => {
 		event.preventDefault();

@@ -420,17 +420,17 @@ class SearchView extends BaseView {
 			this.showAlertOverlay(e.toString());
 			console.error(e);
 		}
-	}
+	};
 
-	mouseOverHandler (name) { this.location[name].suggestionsFocused = true; }
-	mouseOutHandler  (name) { this.location[name].suggestionsFocused = false; }
+	mouseOverHandler = name => { this.location[name].suggestionsFocused = true;  };
+	mouseOutHandler  = name => { this.location[name].suggestionsFocused = false; };
 
 	focusHandler = event => {
 		const name = event.target.name;
 
 		this.location[name].suggestionsVisible = true;
 		this.requestUpdate();
-	}
+	};
 
 	blurHandler = event => {
 		const name = event.target.name;

@@ -439,7 +439,7 @@ class SearchView extends BaseView {
 			this.location[name].suggestionsVisible = false;
 			this.requestUpdate();
 		}
-	}
+	};
 
 	keyupHandler = event => {
 		const name  = event.target.name;

@@ -497,7 +497,7 @@ class SearchView extends BaseView {
 		}
 
 		this.requestUpdate();
-	}
+	};
 
 	inputHandler = async event => {
 		const name  = event.target.name;

@@ -521,7 +521,7 @@ class SearchView extends BaseView {
 
 			this.requestUpdate();
 		};
-	}
+	};
 
 	changeHandler = event => {
 		const name  = event.target.name;

@@ -534,7 +534,8 @@ class SearchView extends BaseView {
 		if (name === 'time')        this.date.setTime(value);
 
 		this.requestUpdate();
-	}
+	};
+
 }
 
 customElements.define('search-view', SearchView);
diff --git a/src/settingsView.js b/src/settingsView.js
@@ -23,6 +23,7 @@ class SettingsView extends BaseView {
 
 	constructor () {
 		super();
+
 		this.viewState = settings.getState();
 	}
 

@@ -44,7 +45,6 @@ class SettingsView extends BaseView {
 		}
 	}
 
-
 	render = () => [
 		html`
 			<div class="flex-row">
diff --git a/src/tripView.js b/src/tripView.js
@@ -31,16 +31,7 @@ class TripView extends BaseView {
 	constructor () {
 		super();
 
-		this.viewState  = null;
-	}
-
-	async connectedCallback () {
-		super.connectedCallback();
-		await sleep(100);
-
-		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
-
-		if (!this.isOffline) await this.updateViewState();
+		this.viewState = null;
 	}
 
 	disconnectedCallback () {

@@ -49,13 +40,18 @@ class TripView extends BaseView {
 		this.viewState = null;
 	}
 
+	async willUpdate (previous) {
+		if (previous.has('stopId')) this.viewState = null;
 
-	async updated (previous) {
-		super.updated(previous);
+		if (!this.viewState) await this.updateViewState();
+	}
+
+	updated (previous) {
+		super.updated();
 
-		if (isDevServer) console.info('updated(): ', previous);
+		if (isDevServer) console.info('TripView(updated): ', previous);
 
-		if (previous.has('isOffline') && this.viewState === null) await this.updateViewState();
+		setThemeColor(queryBackgroundColor(this.renderRoot, 'header'));
 	}
 
 	updateViewState = async () => {

@@ -93,13 +89,13 @@ class TripView extends BaseView {
 				this.requestUpdate();
 			}
 
-			if (isDevServer) console.info('viewState: ',this.viewState);
+			if (isDevServer) console.info('TripView(viewState): ',this.viewState);
 		} catch(e) {
 			this.showAlertOverlay(e.toString());
 			console.error(e);
 		}
 		this.isUpdating = false;
-	}
+	};
 
 	renderView = () => [
 		html`

@@ -212,6 +208,7 @@ class TripView extends BaseView {
 			`
 		)
 	];
+
 }
 
 customElements.define('trip-view', TripView);