zaphyra's git: oeffisearch

fast and simple tripplanner

commit f6e83226cb93cdf78d8c9ecf688453be01fed9f9
parent 198477b7c8bfd67dd51f6dc57aa6d7eff792ac43
Author: Katja Ramona Sophie Kwast (zaphyra) <git@zaphyra.eu>
Date: Mon, 17 Nov 2025 01:47:08 +0100

settings: refactor settingsStore logic, remove `zustand` dependency
13 files changed, 796 insertions(+), 613 deletions(-)
M
flake.nix
|
2
+-
M
package.json
|
3
+--
M
pnpm-lock.yaml
|
23
-----------------------
M
src/app_functions.js
|
10
+++++-----
M
src/baseView.js
|
4
++--
M
src/formatters.js
|
4
++--
M
src/helpers.js
|
1
+
M
src/languages.js
|
6
++++--
M
src/main.js
|
6
+++---
M
src/searchView.js
|
1219
+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
M
src/settings.js
|
123
++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
M
src/templates.js
|
4
++--
M
src/translate.js
|
4
++--
diff --git a/flake.nix b/flake.nix
@@ -53,7 +53,7 @@
         oeffisearch = final.stdenv.mkDerivation (finalAttrs: {
           pname = "oeffisearch";
           version = finalAttrs.env.GIT_VERSION;
-          npmHash = "sha256-27P06lkKqApo32eL8GPh19OE9rT0d4NLopu2+ezWSps=";
+          npmHash = "sha256-VakvVABhQEZyPi2Ssota3CaYPXiouhJEfA72U0KNr9s=";
 
           src = inputs.self;
 
diff --git a/package.json b/package.json
@@ -17,8 +17,7 @@
     "hafas-client": "^6.3.5",
     "ics": "^3.8.1",
     "idb": "^8.0.3",
-    "lit": "^3.3.0",
-    "zustand": "^5.0.5"
+    "lit": "^3.3.0"
   },
   "devDependencies": {
     "@rollup/plugin-commonjs": "^28.0.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
@@ -26,9 +26,6 @@ importers:
       lit:
         specifier: ^3.3.0
         version: 3.3.0
-      zustand:
-        specifier: ^5.0.5
-        version: 5.0.5
     devDependencies:
       '@rollup/plugin-commonjs':
         specifier: ^28.0.3

@@ -2707,24 +2704,6 @@ packages:
   yup@1.6.1:
     resolution: {integrity: sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==}
 
-  zustand@5.0.5:
-    resolution: {integrity: sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==}
-    engines: {node: '>=12.20.0'}
-    peerDependencies:
-      '@types/react': '>=18.0.0'
-      immer: '>=9.0.6'
-      react: '>=18.0.0'
-      use-sync-external-store: '>=1.2.0'
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      immer:
-        optional: true
-      react:
-        optional: true
-      use-sync-external-store:
-        optional: true
-
 snapshots:
 
   '@ampproject/remapping@2.3.0':

@@ -5724,5 +5703,3 @@ snapshots:
       tiny-case: 1.0.3
       toposort: 2.0.2
       type-fest: 2.19.0
-
-  zustand@5.0.5: {}
diff --git a/src/app_functions.js b/src/app_functions.js
@@ -1,5 +1,5 @@
 import { db } from './dataStorage.js';
-import { settingsState } from './settings.js';
+import { settings } from './settings.js';
 import { getHafasClient, client } from './hafasClient.js';
 import { trainsearchToHafas, hafasToTrainsearch } from './refresh_token/index.js';
 import { CustomDate } from './helpers.js';

@@ -56,9 +56,9 @@ const journeySettings = () => {
 		language:  t('backendLang'),
 	};
 
-	if (settingsState.profile === 'db') {
-		params.loyaltyCard = settingsState.loyaltyCard;
-		params.ageGroup    = settingsState.ageGroup;
+	if (settings.profile === 'db') {
+		params.loyaltyCard = settings.loyaltyCard;
+		params.ageGroup    = settings.ageGroup;
 	}
 
 	return params;

@@ -101,7 +101,7 @@ export const newJourneys = async params => {
 	data.indexOffset = 0;
 	data.params      = params;
 	data.settings    = journeySettingsObj;
-	data.profile     = settingsState.profile;
+	data.profile     = settings.profile;
 
 	await addJourneys(data);
 
diff --git a/src/baseView.js b/src/baseView.js
@@ -30,7 +30,7 @@ export class BaseView extends LitElement {
 		this.isOffline  = !navigator.onLine;
 		this.isUpdating = false;
 
-		this.settingsState = settings.getState();
+		this.settingsState = settings;
 		this.overlayState = {
 			type:     'plain',
 			visible:  false,

@@ -45,7 +45,7 @@ export class BaseView extends LitElement {
 
 		this._unsubscribeSettingsState = settings.subscribe(state => {
 			this.settingsState = state;
-			this.performUpdate();
+			this.requestUpdate();
 		});
 
 		window.addEventListener('online',  this.connectionHandler);
diff --git a/src/formatters.js b/src/formatters.js
@@ -1,6 +1,6 @@
 import { getDS100byIBNR } from './ds100.js';
 import { padZeros } from './helpers.js';
-import { settingsState } from './settings.js';
+import { settings } from './settings.js';
 
 export const formatPoint = point => {
 	switch (point.type) {

@@ -8,7 +8,7 @@ export const formatPoint = point => {
 		case 'station':
 			let station = point.name;
 
-			if (settingsState.showDS100) {
+			if (settings.showDS100) {
 				const ds100 = getDS100byIBNR(point.id);
 				if (ds100 !== null) station += ` (${ds100})`;
 			}
diff --git a/src/helpers.js b/src/helpers.js
@@ -1,6 +1,7 @@
 export const sleep         = delay => new Promise((resolve) => setTimeout(resolve, delay));
 export const isEmptyObject = obj   => Object.keys(obj).length === 0;
 export const padZeros      = str   => (('00' + str).slice(-2));
+export const upperFirst    = str => str[0].toUpperCase() + str.slice(1);
 
 export const setThemeColor        = color           => document.querySelector('meta[name="theme-color"]').setAttribute('content', color);
 export const queryBackgroundColor = (target, query) => window.getComputedStyle(target.querySelector(query)).getPropertyValue('background-color');
diff --git a/src/languages.js b/src/languages.js
@@ -86,7 +86,8 @@ export const languages = {
 		'minTransferTime':         'Transfer time (Minutes)',
 		'trainType':               'Train type',
 		'close':                   'Close',
-		'addCalendar':             'Export as ICS'
+		'addCalendar':             'Export as ICS',
+		'history':                 'History'
 	},
 
 	'de': {

@@ -177,7 +178,8 @@ export const languages = {
 		'price':                   'Preis',
 		'refresh':                 'Aktualisieren',
 		'back':                    'Zurück',
-		'addCalendar':             'Als ICS exportieren'
+		'addCalendar':             'Als ICS exportieren',
+		'history':                 'Verlauf'
 	},
 
 	'nl': {
diff --git a/src/main.js b/src/main.js
@@ -3,7 +3,7 @@ import { cache } from 'lit/directives/cache.js';
 
 import { initDataStorage }  from './dataStorage.js';
 import { initHafasClient }  from './hafasClient.js';
-import { initSettingsState, settingsState } from './settings.js'
+import { initSettings, settings } from './settings.js'
 
 import { baseStyles } from './styles.js';
 

@@ -70,9 +70,9 @@ class Oeffisearch extends LitElement {
 customElements.define('oeffi-search', Oeffisearch);
 
 window.addEventListener('load', async () => {
-	await initSettingsState();
+	await initSettings();
 	await initDataStorage();
-	await initHafasClient(settingsState.profile);
+	await initHafasClient(settings.profile);
 
 	const style = document.createElement('style');
 	style.type = 'text/css';
diff --git a/src/searchView.js b/src/searchView.js
@@ -1,522 +1,705 @@
-import { LitElement, html, nothing } from 'lit';
-import { classMap } from 'lit/directives/class-map.js';
-import { when } from 'lit/directives/when.js';
-import { BaseView } from './baseView.js';
-
-import { db } from './dataStorage.js';
-import { t } from './translate.js';
-import { client } from './hafasClient.js';
-import { getIBNRbyDS100 } from './ds100.js';
-import { formatPoint } from './formatters.js';
-import { newJourneys } from './app_functions.js';
-import { CustomDate, sleep, queryBackgroundColor, setThemeColor } from './helpers.js';
-
-import { searchViewStyles } from './styles.js';
+import { LitElement, html, nothing } from "lit";
+import { classMap } from "lit/directives/class-map.js";
+import { when } from "lit/directives/when.js";
+import { BaseView } from "./baseView.js";
+
+import { db } from "./dataStorage.js";
+import { t } from "./translate.js";
+import { client } from "./hafasClient.js";
+import { getIBNRbyDS100 } from "./ds100.js";
+import { formatPoint } from "./formatters.js";
+import { newJourneys } from "./app_functions.js";
+import {
+  CustomDate,
+  sleep,
+  queryBackgroundColor,
+  setThemeColor,
+} from "./helpers.js";
+
+import { searchViewStyles } from "./styles.js";
 
 class SearchView extends BaseView {
-	static properties = {
-		date:          { state: true },
-		numEnter:      { state: true },
-		noTransfers:   { state: true },
-		isArrival:     { state: true },
-		showHistory:   { state: true },
-		history:       { state: true },
-		location:      { state: true },
-	};
-
-	static styles = [
-		super.styles,
-		searchViewStyles
-	];
-
-	constructor () {
-		super();
-
-		this.date          = new CustomDate();
-		this.numEnter      = 0;
-		this.noTransfers   = false,
-		this.isArrival     = false,
-		this.showHistory   = false;
-		this.history       = [];
-		this.location      = {
-			from: {
-				value: '',
-				suggestionsVisible: false,
-				suggestionSelected: null,
-				suggestion: null,
-				suggestions: [],
-			},
-			to: {
-				value: '',
-				suggestionsVisible: false,
-				suggestionSelected: null,
-				suggestion: null,
-				suggestions: [],
-			},
-			via: {
-				value: '',
-				suggestionsVisible: false,
-				suggestionSelected: null,
-				suggestion: null,
-				suggestions: [],
-			},
-		};
-	}
-
-	async connectedCallback () {
-		super.connectedCallback();
-
-		this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse();
-
-		await sleep(200);
-		setThemeColor(queryBackgroundColor(document, 'body'));
-		await sleep(200);
-		this.renderRoot.querySelector('input[name=from]').focus();
-	}
-
-	async willUpdate (previous) {
-		if (
-			previous.has('settingsState') &&
-			previous.get('settingsState') !== undefined &&
-			previous.get('settingsState').profile !== this.settingsState.profile
-		) await this.profileChangeHandler();
-	}
-
-	updated (previous) {
-		super.updated(previous, 'SearchView');
-	}
-
-	renderView = () => {
-		return html`
-			<div class="container">
-				<div class="title flex-center"><h1>${APP_NAME}</h1></div>
-
-				<form class="center" id="form" @submit=${this.submitHandler}>
-					${['from', 'via', 'to'].map(name => html`
-					<div class="flex-row nowrap ${name === 'via' && !this.settingsState.showVia ? 'hidden' : ''}">
-						<input type="text" class="flex-grow" name="${name}" title="${t(name)}" placeholder="${t(name)}" .value=${this.location[name].value}
-						@focus=${this.focusHandler}
-						@blur=${this.blurHandler}
-						@keydown=${this.keydownHandler}
-						@keyup=${this.keyupHandler}
-						@input=${this.inputHandler} 
-						autocomplete="off"  ?required=${name !== 'via'}>
-
-						${when(
-							name === 'from',
-							() => html`
-								<div class="button icon-arrow ${classMap({ flipped: this.settingsState.showVia })}" tabindex="0" title="${t('via')}"
-								@keydown=${this.keyClickHandler} @click=${this.settingsState.toggleShowVia}></div>
-							`
-						)}
-						${when(
-							name === 'via',
-							() => html`<div class="button icon-arrow invisible"></div>`
-						)}
-						${when(
-							name === 'to',
-							() => html`
-								<div class="button icon-swap" tabindex="0" title=${t('swap')}
-								@keydown=${this.keyClickHandler} @click=${this.swapFromTo}></div>
-							`
-						)}
-					</div>
-
-					<div class="suggestions ${this.location[name].suggestionsVisible ? '' : 'hidden'}">
-					${this.location[name].suggestions.map((suggestion, index) => html`
-						<p class="${index !== this.location[name].suggestion ? nothing : 'selected'}"
-						@click=${(event) => this.setSuggestion(name, index, event.pointerType)}
-						@mouseover=${() => this.mouseOverHandler(name)}
-						@mouseout=${() => this.mouseOutHandler(name)}>${formatPoint(suggestion)}</p>
-					`)}
-					</div>
-					`)}
-
-					<div class="flex-row">
-						<div class="selector">
-							<input type="radio" id="departure" name="isArrival" value="0" @change=${this.changeHandler} .checked=${!this.isArrival}>
-							<label for="departure" tabindex=0 @keydown=${this.keyClickHandler}>${t('departure')}</label>
-							<input type="radio" id="arrival" name="isArrival" value="1" @change=${this.changeHandler} .checked=${this.isArrival}>
-							<label for="arrival" tabindex=0 @keydown=${this.keyClickHandler}>${t('arrival')}</label>
-						</div>
-
-						<div class="button now" tabindex=0 title="${t('titleSetDateTimeNow')}"
-						@keydown=${this.keyClickHandler} @click=${this.resetDate}>${t('now')}</div>
-						${!this.settingsState.combineDateTime ? html`
-						<input type="time" name="time" title="${t('time')}" class="flex-grow" @change=${this.changeHandler} .value=${this.date.formatTime()} required>
-						<input type="date" name="date" title="${t('date')}" class="flex-grow" @change=${this.changeHandler} .value=${this.date.formatISODate()} required>
-						` : html`
-						<input type="datetime-local" name="dateTime" title="${t('date')} & ${t('time')}" class="flex-grow"
-						@change=${this.changeHandler} .value=${this.date.formatISODateTime()} required>
-						`}
-					</div>
-
-					<div class="flex-row">
-						<div class="selector rectangular">
-						${client.profile.products.map(product => html`
-							<input type="checkbox" name="${product.id}" id="${product.id}"
-							@change=${(event) => this.settingsState.toggleProduct(event.target.name)} .checked=${this.settingsState.products[product.id] ?? true}>
-							<label class="${this.iconForProduct(product.id)}" for="${product.id}" title="${t('product')}: ${product.name}"></label>
-						`)}
-						</div>
-
-						<div class="selector rectangular">
-							<input type="checkbox" id="bikeFriendly" name="bikeFriendly" @change=${this.settingsState.toggleBikeFriendly} .checked=${this.settingsState.bikeFriendly}>
-							<label class="icon-bike" for="bikeFriendly" title="${t('titleBikeFriendly')}"></label>
-						</div>
-
-						<div class="selector rectangular">
-							<input type="checkbox" id="noTransfers" name="noTransfers" @change=${this.changeHandler} .checked=${this.noTransfers}>
-							<label class="icon-seat" for="noTransfers" title="${t('titleNoTransfers')}"></label>
-						</div>
-
-						<div class="filler"></div>
-
-						<div class="button icon-settings" title="${t('settings')}" @click=${this.showSettings}></div>
-						<button type="submit" tabindex="0" id="submit">${t('search')}</button>
-					</div>
-
-					${this.history.length !== 0 ? html`
-					<div id="historyButton" class="arrowButton icon-arrow ${classMap({ flipped: this.showHistory })}" title=${t('history')} @click=${this.toggleHistory}></div>
-					` : nothing}
-				</form>
-
-				${when(
-					this.showHistory,
-					() => html`
-						<div id="history" class="history center">
-						${this.history.map((element, index) => html`
-							<div class="flex-row" @click="${() => this.journeysHistoryAction(index)}">
-								<div class="from">
-									<small>${t('from')}:</small>
-									${formatPoint(element.fromPoint)}
-									${element.viaPoint ? html`<div class="via">${t('via')} ${formatPoint(element.viaPoint)}</div>` : nothing}
-								</div>
-								<div class="icon-arrow1"></div>
-								<div class="to">
-									<small>${t('to')}:</small>
-									${formatPoint(element.toPoint)}
-								</div>
-							</div>
-						`)}
-						</div>
-					`
-				)}
-				<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;
-
-	journeysHistoryAction = num => {
-		const element = this.history[num];
-
-		const options = [
-			{'label': t('setfromto'),       'action': () => { this.setFromHistory(element.key); this.hideOverlay(); }},
-			{'label': t('journeyoverview'), 'action': () => { window.location = `#/${element.slug}/${this.settingsState.journeysViewMode}`; this.hideOverlay(); }}
-		];
-
-		if (element.lastSelectedJourneyId !== undefined) {
-			options.push({
-				'label':  t('lastSelectedJourney'),
-				'action': () => { window.location = `#/j/${this.settingsState.profile}/${element.lastSelectedJourneyId}`; this.hideOverlay(); }
-			});
-		}
-
-		this.showSelectOverlay(options);
-	};
-
-	 setFromHistory = async id => {
-		const entry = await db.getHistoryEntry(id);
-		if (!entry) return;
-
-		[ 'from', 'via', 'to' ].forEach(mode => {
-			if ( entry[`${mode}Point`] === null) return false;
-			if (mode === 'via') this.settingsState.setShowVia(true);
-
-			this.location[mode].value              = formatPoint(entry[`${mode}Point`]);
-			this.location[mode].suggestionSelected = entry[`${mode}Point`];
-		});
-
-		this.requestUpdate();
-	};
-
-	iconForProduct = id => {
-		const productIcons = {
-			// DB
-			"nationalExpress": "icon-ice",
-			"national":        "icon-ic",
-			"regionalExpress": "icon-dzug",
-
-			// BVG
-			"express": "icon-icice",
-
-			// nahsh
-			"interregional": "icon-dzug",
-			"onCall":        "icon-taxi",
-
-			// SNCB
-			"intercity-p": "icon-ic",
-			"s-train":     "icon-suburban",
-
-			// RMV
-			"express-train":       "icon-ice",
-			"long-distance-train": "icon-ic",
-			"regiona-train":       "icon-regional",
-			"s-bahn":              "icon-suburban",
-			"u-bahn":              "icon-subway",
-			"watercraft":          "icon-ferry",
-			"ast":                 "icon-taxi",
-
-			// Rejseplanen
-			"national-train":      "icon-ic",
-			"national-train-2":    "icon-icl",
-			"local-train":         "icon-re",
-			"o":                   "icon-o",
-			"s-tog":               "icon-suburban",
-		};
-
-		return productIcons[id] || `icon-${id}`;
-	};
-
-	focusNextElement = currentElementId => {
-		switch (currentElementId) {
-			case 'from':
-				this.renderRoot.querySelector('input[name=to]').focus();
-
-				if (this.settingsState.showVia) this.renderRoot.querySelector('input[name=via]').focus();
-				break;
-
-			case 'via':
-				this.renderRoot.querySelector('input[name=to]').focus();
-				break;
-
-			case 'to':
-				this.renderRoot.querySelector('[type=submit]').focus();
-				break;
-		}
-	};
-
-	setSuggestion = (name, num, pointerType) => {
-		this.location[name].value              = formatPoint(this.location[name].suggestions[num]);
-		this.location[name].suggestionSelected = this.location[name].suggestions[num];
-		this.location[name].suggestionsVisible = false;
-		this.requestUpdate();
-		if (pointerType) this.focusNextElement(name);
-	};
-
-	profileChangeHandler = async () => {
-		[ 'from', 'via','to' ].forEach(name => {
-			this.location[name] = {
-				value: '',
-				suggestionsVisible: false,
-				suggestionSelected: null,
-				suggestion: null,
-				suggestions: [],
-			};
-		});
-
-		this.history = (await db.getHistory(this.settingsState.profile)).slice().reverse();
-	};
-
-	submitHandler = async event => {
-		event.preventDefault();
-
-		if (this.isOffline !== false) {
-			this.showAlertOverlay(t('offline'));
-			return;
-		}
-
-		const params = {
-			from: null,
-			to: null,
-			via: null,
-			results: 6,
-			products: {},
-			bike: this.settingsState.bikeFriendly,
-			transferTime: this.settingsState.transferTime,
-		};
-
-		await Promise.all([ 'from', 'via', 'to' ].map(async mode => {
-			if (this.location[mode].value !== '') {
-				if (mode === 'via' && !this.settingsState.showVia) return false;
-
-				if (!this.location[mode].suggestionSelected) {
-					if (this.location[mode].suggestions.length !== 0) {
-						params[mode] = this.location[mode].suggestions[0]
-					} else {
-						const data = await client.locations(this.location[mode].value, {'results': 1});
-						if (!data[0]) return false;
-						params[mode] = data[0];
-					}
-				} else {
-					params[mode] = this.location[mode].suggestionSelected;
-				}
-			}
-		}));
-
-		if (!params.from || !params.to) return false;
-
-		if (formatPoint(params.from) === formatPoint(params.to) && params.via === null) {
-			this.showAlertOverlay('From and To are the same place.');
-			return false;
-		};
-
-		client.profile.products.forEach(product => {
-			params.products[product.id] = this.settingsState.products[product.id] ?? true;
-		});
-
-		if (this.noTransfers) params.transfers = 0;
-
-		if (!this.isArrival)  params.departure = this.date.getTime();
-		else                  params.arrival   = this.date.getTime();
-
-		if (this.settingsState.profile !== 'db') {
-			params.accessibility = this.settingsState.accessibility;
-			params.walkingSpeed  = this.settingsState.walkingSpeed;
-		}
-
-		if (isDevServer) console.info('SearchView(params):',params);
-
-		try {
-			this.showLoaderOverlay();
-			const responseData = await newJourneys(params);
-
-			window.location = `#/${responseData.slug}/${this.settingsState.journeysViewMode}`;
-			this.hideOverlay();
-		} catch(e) {
-			this.showAlertOverlay(e.toString());
-			console.error(e);
-		}
-	};
-
-	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;
-
-		if (!this.location[name].suggestionsFocused) {
-			this.location[name].suggestionsVisible = false;
-			this.requestUpdate();
-		}
-	};
-
-	keyupHandler = event => {
-		const name  = event.target.name;
-		const value = event.target.value;
-
-		if (event.key !== 'Enter') return true;
-
-		if (this.numEnter === 2 && value === formatPoint(this.location[name].suggestionSelected)) {
-			this.numEnter = 0;
-			this.focusNextElement(name);
-		}
-	};
-
-	keydownHandler = event => {
-		const name = event.target.name;
-
-		if (this.location[name].suggestions.length === 0) return true;
-	
-		if (event.key === 'Enter') {
-			event.preventDefault();
-			this.numEnter++;
-			this.setSuggestion(name, this.location[name].suggestion);
-			return true;
-		};
-
-		if (!this.location[name].suggestionsVisible) {
-			this.numEnter = 0;
-			this.location[name].suggestionsVisible = true;
-			this.requestUpdate();
-			return true;
-		}
-
-		if (['Escape', 'Tab'].includes(event.key)) {
-			this.location[name].suggestionsVisible = false;
-		}
-
-		if (['ArrowUp', 'ArrowDown'].includes(event.key) && !event.shiftKey) {
-			event.preventDefault();
-
-			const numSuggesttions = this.location[name].suggestions.length-1;
-
-			if (event.key === 'ArrowUp') {
-				if (this.location[name].suggestion === 0) {
-					this.location[name].suggestion = numSuggesttions;
-				} else {
-					this.location[name].suggestion--;
-				}
-			} else {
-				if (this.location[name].suggestion === numSuggesttions) {
-					this.location[name].suggestion = 0;
-				} else {
-					this.location[name].suggestion++;
-				}
-			}
-		}
-
-		this.requestUpdate();
-	};
-
-	inputHandler = async event => {
-		const name  = event.target.name;
-		const value = event.target.value.trim();
-
-		if (['from', 'via', 'to'].includes(name)) { 
-			if (this.isOffline !== false) return;
-			if (value === '') return;
-
-			this.location[name].value = value;
-
-			let   suggestions = [];
-			const ds100Result = getIBNRbyDS100(value.toUpperCase());
-		
-			if (ds100Result !== null) suggestions = await client.locations(ds100Result, {'results':  1})
-
-			suggestions = suggestions.concat(await client.locations(value, {'results': 10}));
-
-			this.location[name].suggestionSelected = null;
-			this.location[name].suggestion         = 0;
-			this.location[name].suggestions        = suggestions;
-
-			this.requestUpdate();
-		};
-	};
-
-	changeHandler = event => {
-		const name  = event.target.name;
-		const value = event.target.value;
-
-		if (name === 'noTransfers') this.noTransfers = !this.noTransfers;
-		if (name === 'isArrival')   this.isArrival   = !this.isArrival;
-		if (name === 'dateTime')    this.date.setDateTime(value);
-		if (name === 'date')        this.date.setDate(value);
-		if (name === 'time')        this.date.setTime(value);
-
-		this.requestUpdate();
-	};
-
+  static properties = {
+    date: { state: true },
+    numEnter: { state: true },
+    noTransfers: { state: true },
+    isArrival: { state: true },
+    showHistory: { state: true },
+    history: { state: true },
+    location: { state: true },
+  };
+
+  static styles = [super.styles, searchViewStyles];
+
+  constructor() {
+    super();
+
+    this.date = new CustomDate();
+    this.numEnter = 0;
+    ((this.noTransfers = false),
+      (this.isArrival = false),
+      (this.showHistory = false));
+    this.history = [];
+    this.location = {
+      from: {
+        value: "",
+        suggestionsVisible: false,
+        suggestionSelected: null,
+        suggestion: null,
+        suggestions: [],
+      },
+      to: {
+        value: "",
+        suggestionsVisible: false,
+        suggestionSelected: null,
+        suggestion: null,
+        suggestions: [],
+      },
+      via: {
+        value: "",
+        suggestionsVisible: false,
+        suggestionSelected: null,
+        suggestion: null,
+        suggestions: [],
+      },
+    };
+  }
+
+  async connectedCallback() {
+    super.connectedCallback();
+
+    this.history = (await db.getHistory(this.settingsState.profile))
+      .slice()
+      .reverse();
+
+    await sleep(200);
+    setThemeColor(queryBackgroundColor(document, "body"));
+    await sleep(200);
+    this.renderRoot.querySelector("input[name=from]").focus();
+  }
+
+  async willUpdate(previous) {
+    if (
+      previous.has("settingsState") &&
+      previous.get("settingsState") !== undefined &&
+      previous.get("settingsState").profile !== this.settingsState.profile
+    )
+      await this.profileChangeHandler();
+  }
+
+  updated(previous) {
+    super.updated(previous, "SearchView");
+  }
+
+  renderView = () => {
+    return html`
+      <div class="container">
+        <div class="title flex-center"><h1>${APP_NAME}</h1></div>
+
+        <form class="center" id="form" @submit=${this.submitHandler}>
+          ${["from", "via", "to"].map(
+            (name) => html`
+              <div
+                class="flex-row nowrap ${name === "via" &&
+                !this.settingsState.showVia
+                  ? "hidden"
+                  : ""}"
+              >
+                <input
+                  type="text"
+                  class="flex-grow"
+                  name="${name}"
+                  title="${t(name)}"
+                  placeholder="${t(name)}"
+                  .value=${this.location[name].value}
+                  @focus=${this.focusHandler}
+                  @blur=${this.blurHandler}
+                  @keydown=${this.keydownHandler}
+                  @keyup=${this.keyupHandler}
+                  @input=${this.inputHandler}
+                  autocomplete="off"
+                  ?required=${name !== "via"}
+                />
+
+                ${when(
+                  name === "from",
+                  () => html`
+                    <div
+                      class="button icon-arrow ${classMap({
+                        flipped: this.settingsState.showVia,
+                      })}"
+                      tabindex="0"
+                      title="${t("via")}"
+                      @keydown=${this.keyClickHandler}
+                      @click=${this.settingsState.toggleShowVia}
+                    ></div>
+                  `,
+                )}
+                ${when(
+                  name === "via",
+                  () => html`<div class="button icon-arrow invisible"></div>`,
+                )}
+                ${when(
+                  name === "to",
+                  () => html`
+                    <div
+                      class="button icon-swap"
+                      tabindex="0"
+                      title=${t("swap")}
+                      @keydown=${this.keyClickHandler}
+                      @click=${this.swapFromTo}
+                    ></div>
+                  `,
+                )}
+              </div>
+
+              <div
+                class="suggestions ${this.location[name].suggestionsVisible
+                  ? ""
+                  : "hidden"}"
+              >
+                ${this.location[name].suggestions.map(
+                  (suggestion, index) => html`
+                    <p
+                      class="${index !== this.location[name].suggestion
+                        ? nothing
+                        : "selected"}"
+                      @click=${(event) =>
+                        this.setSuggestion(name, index, event.pointerType)}
+                      @mouseover=${() => this.mouseOverHandler(name)}
+                      @mouseout=${() => this.mouseOutHandler(name)}
+                    >
+                      ${formatPoint(suggestion)}
+                    </p>
+                  `,
+                )}
+              </div>
+            `,
+          )}
+
+          <div class="flex-row">
+            <div class="selector">
+              <input
+                type="radio"
+                id="departure"
+                name="isArrival"
+                value="0"
+                @change=${this.changeHandler}
+                .checked=${!this.isArrival}
+              />
+              <label
+                for="departure"
+                tabindex="0"
+                @keydown=${this.keyClickHandler}
+                >${t("departure")}</label
+              >
+              <input
+                type="radio"
+                id="arrival"
+                name="isArrival"
+                value="1"
+                @change=${this.changeHandler}
+                .checked=${this.isArrival}
+              />
+              <label for="arrival" tabindex="0" @keydown=${this.keyClickHandler}
+                >${t("arrival")}</label
+              >
+            </div>
+
+            <div
+              class="button now"
+              tabindex="0"
+              title="${t("titleSetDateTimeNow")}"
+              @keydown=${this.keyClickHandler}
+              @click=${this.resetDate}
+            >
+              ${t("now")}
+            </div>
+            ${!this.settingsState.combineDateTime
+              ? html`
+                  <input
+                    type="time"
+                    name="time"
+                    title="${t("time")}"
+                    class="flex-grow"
+                    @change=${this.changeHandler}
+                    .value=${this.date.formatTime()}
+                    required
+                  />
+                  <input
+                    type="date"
+                    name="date"
+                    title="${t("date")}"
+                    class="flex-grow"
+                    @change=${this.changeHandler}
+                    .value=${this.date.formatISODate()}
+                    required
+                  />
+                `
+              : html`
+                  <input
+                    type="datetime-local"
+                    name="dateTime"
+                    title="${t("date")} & ${t("time")}"
+                    class="flex-grow"
+                    @change=${this.changeHandler}
+                    .value=${this.date.formatISODateTime()}
+                    required
+                  />
+                `}
+          </div>
+
+          <div class="flex-row">
+            <div class="selector rectangular">
+              ${client.profile.products.map(
+                (product) => html`
+                  <input
+                    type="checkbox"
+                    name="${product.id}"
+                    id="${product.id}"
+                    @click=${() => this.settingsState.toggleProduct(product.id)}
+                    .checked=${this.settingsState.products[product.id] ?? true}
+                  />
+                  <label
+                    class="${this.iconForProduct(product.id)}"
+                    for="${product.id}"
+                    title="${t("product")}: ${product.name}"
+                  ></label>
+                `,
+              )}
+            </div>
+
+            <div class="selector rectangular">
+              <input
+                type="checkbox"
+                id="bikeFriendly"
+                name="bikeFriendly"
+                @change=${this.settingsState.toggleBikeFriendly}
+                .checked=${this.settingsState.bikeFriendly}
+              />
+              <label
+                class="icon-bike"
+                for="bikeFriendly"
+                title="${t("titleBikeFriendly")}"
+              ></label>
+            </div>
+
+            <div class="selector rectangular">
+              <input
+                type="checkbox"
+                id="noTransfers"
+                name="noTransfers"
+                @change=${this.changeHandler}
+                .checked=${this.noTransfers}
+              />
+              <label
+                class="icon-seat"
+                for="noTransfers"
+                title="${t("titleNoTransfers")}"
+              ></label>
+            </div>
+
+            <div class="filler"></div>
+
+            <div
+              class="button icon-settings"
+              title="${t("settings")}"
+              @click=${this.showSettings}
+            ></div>
+            <button type="submit" tabindex="0" id="submit">
+              ${t("search")}
+            </button>
+          </div>
+        </form>
+
+        ${when(
+          this.history.length !== 0,
+          () => html`
+            <div id="history" class="history center">
+              <h3>${t('history')}</h3>
+              ${this.history.map(
+                (element, index) => html`
+                  <div
+                    class="flex-row"
+                    @click="${() => this.journeysHistoryAction(index)}"
+                  >
+                    <div class="from">
+                      <small>${t("from")}:</small>
+                      ${formatPoint(element.fromPoint)}
+                      ${element.viaPoint
+                        ? html`<div class="via">
+                            ${t("via")} ${formatPoint(element.viaPoint)}
+                          </div>`
+                        : nothing}
+                    </div>
+                    <div class="icon-arrow1"></div>
+                    <div class="to">
+                      <small>${t("to")}:</small>
+                      ${formatPoint(element.toPoint)}
+                    </div>
+                  </div>
+                `,
+              )}
+            </div>
+          `,
+        )}
+        <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);
+
+  journeysHistoryAction = (num) => {
+    const element = this.history[num];
+
+    const options = [
+      {
+        label: t("setfromto"),
+        action: () => {
+          this.setFromHistory(element.key);
+          this.hideOverlay();
+        },
+      },
+      {
+        label: t("journeyoverview"),
+        action: () => {
+          window.location = `#/${element.slug}/${this.settingsState.journeysViewMode}`;
+          this.hideOverlay();
+        },
+      },
+    ];
+
+    if (element.lastSelectedJourneyId !== undefined) {
+      options.push({
+        label: t("lastSelectedJourney"),
+        action: () => {
+          window.location = `#/j/${this.settingsState.profile}/${element.lastSelectedJourneyId}`;
+          this.hideOverlay();
+        },
+      });
+    }
+
+    this.showSelectOverlay(options);
+  };
+
+  setFromHistory = async (id) => {
+    const entry = await db.getHistoryEntry(id);
+    if (!entry) return;
+
+    ["from", "via", "to"].forEach((mode) => {
+      if (entry[`${mode}Point`] === null) return false;
+      if (mode === "via") this.settingsState.setShowVia(true);
+
+      this.location[mode].value = formatPoint(entry[`${mode}Point`]);
+      this.location[mode].suggestionSelected = entry[`${mode}Point`];
+    });
+
+    this.requestUpdate();
+  };
+
+  iconForProduct = (id) => {
+    const productIcons = {
+      // DB
+      nationalExpress: "icon-ice",
+      national: "icon-ic",
+      regionalExpress: "icon-dzug",
+
+      // BVG
+      express: "icon-icice",
+
+      // nahsh
+      interregional: "icon-dzug",
+      onCall: "icon-taxi",
+
+      // SNCB
+      "intercity-p": "icon-ic",
+      "s-train": "icon-suburban",
+
+      // RMV
+      "express-train": "icon-ice",
+      "long-distance-train": "icon-ic",
+      "regiona-train": "icon-regional",
+      "s-bahn": "icon-suburban",
+      "u-bahn": "icon-subway",
+      watercraft: "icon-ferry",
+      ast: "icon-taxi",
+
+      // Rejseplanen
+      "national-train": "icon-ic",
+      "national-train-2": "icon-icl",
+      "local-train": "icon-re",
+      o: "icon-o",
+      "s-tog": "icon-suburban",
+    };
+
+    return productIcons[id] || `icon-${id}`;
+  };
+
+  focusNextElement = (currentElementId) => {
+    switch (currentElementId) {
+      case "from":
+        this.renderRoot.querySelector("input[name=to]").focus();
+
+        if (this.settingsState.showVia)
+          this.renderRoot.querySelector("input[name=via]").focus();
+        break;
+
+      case "via":
+        this.renderRoot.querySelector("input[name=to]").focus();
+        break;
+
+      case "to":
+        this.renderRoot.querySelector("[type=submit]").focus();
+        break;
+    }
+  };
+
+  setSuggestion = (name, num, pointerType) => {
+    this.location[name].value = formatPoint(
+      this.location[name].suggestions[num],
+    );
+    this.location[name].suggestionSelected =
+      this.location[name].suggestions[num];
+    this.location[name].suggestionsVisible = false;
+    this.requestUpdate();
+    if (pointerType) this.focusNextElement(name);
+  };
+
+  profileChangeHandler = async () => {
+    ["from", "via", "to"].forEach((name) => {
+      this.location[name] = {
+        value: "",
+        suggestionsVisible: false,
+        suggestionSelected: null,
+        suggestion: null,
+        suggestions: [],
+      };
+    });
+
+    this.history = (await db.getHistory(this.settingsState.profile))
+      .slice()
+      .reverse();
+  };
+
+  submitHandler = async (event) => {
+    event.preventDefault();
+
+    if (this.isOffline !== false) {
+      this.showAlertOverlay(t("offline"));
+      return;
+    }
+
+    const params = {
+      from: null,
+      to: null,
+      via: null,
+      results: 6,
+      products: {},
+      bike: this.settingsState.bikeFriendly,
+      transferTime: this.settingsState.transferTime,
+    };
+
+    await Promise.all(
+      ["from", "via", "to"].map(async (mode) => {
+        if (this.location[mode].value !== "") {
+          if (mode === "via" && !this.settingsState.showVia) return false;
+
+          if (!this.location[mode].suggestionSelected) {
+            if (this.location[mode].suggestions.length !== 0) {
+              params[mode] = this.location[mode].suggestions[0];
+            } else {
+              const data = await client.locations(this.location[mode].value, {
+                results: 1,
+              });
+              if (!data[0]) return false;
+              params[mode] = data[0];
+            }
+          } else {
+            params[mode] = this.location[mode].suggestionSelected;
+          }
+        }
+      }),
+    );
+
+    if (!params.from || !params.to) return false;
+
+    if (
+      formatPoint(params.from) === formatPoint(params.to) &&
+      params.via === null
+    ) {
+      this.showAlertOverlay("From and To are the same place.");
+      return false;
+    }
+
+    client.profile.products.forEach((product) => {
+      params.products[product.id] =
+        this.settingsState.products[product.id] ?? true;
+    });
+
+    if (this.noTransfers) params.transfers = 0;
+
+    if (!this.isArrival) params.departure = this.date.getTime();
+    else params.arrival = this.date.getTime();
+
+    if (this.settingsState.profile !== "db") {
+      params.accessibility = this.settingsState.accessibility;
+      params.walkingSpeed = this.settingsState.walkingSpeed;
+    }
+
+    if (isDevServer) console.info("SearchView(params):", params);
+
+    try {
+      this.showLoaderOverlay();
+      const responseData = await newJourneys(params);
+
+      window.location = `#/${responseData.slug}/${this.settingsState.journeysViewMode}`;
+      this.hideOverlay();
+    } catch (e) {
+      this.showAlertOverlay(e.toString());
+      console.error(e);
+    }
+  };
+
+  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;
+
+    if (!this.location[name].suggestionsFocused) {
+      this.location[name].suggestionsVisible = false;
+      this.requestUpdate();
+    }
+  };
+
+  keyupHandler = (event) => {
+    const name = event.target.name;
+    const value = event.target.value;
+
+    if (event.key !== "Enter") return true;
+
+    if (
+      this.numEnter === 2 &&
+      value === formatPoint(this.location[name].suggestionSelected)
+    ) {
+      this.numEnter = 0;
+      this.focusNextElement(name);
+    }
+  };
+
+  keydownHandler = (event) => {
+    const name = event.target.name;
+
+    if (this.location[name].suggestions.length === 0) return true;
+
+    if (event.key === "Enter") {
+      event.preventDefault();
+      this.numEnter++;
+      this.setSuggestion(name, this.location[name].suggestion);
+      return true;
+    }
+
+    if (!this.location[name].suggestionsVisible) {
+      this.numEnter = 0;
+      this.location[name].suggestionsVisible = true;
+      this.requestUpdate();
+      return true;
+    }
+
+    if (["Escape", "Tab"].includes(event.key)) {
+      this.location[name].suggestionsVisible = false;
+    }
+
+    if (["ArrowUp", "ArrowDown"].includes(event.key) && !event.shiftKey) {
+      event.preventDefault();
+
+      const numSuggesttions = this.location[name].suggestions.length - 1;
+
+      if (event.key === "ArrowUp") {
+        if (this.location[name].suggestion === 0) {
+          this.location[name].suggestion = numSuggesttions;
+        } else {
+          this.location[name].suggestion--;
+        }
+      } else {
+        if (this.location[name].suggestion === numSuggesttions) {
+          this.location[name].suggestion = 0;
+        } else {
+          this.location[name].suggestion++;
+        }
+      }
+    }
+
+    this.requestUpdate();
+  };
+
+  inputHandler = async (event) => {
+    const name = event.target.name;
+    const value = event.target.value.trim();
+
+    if (["from", "via", "to"].includes(name)) {
+      if (this.isOffline !== false) return;
+      if (value === "") return;
+
+      this.location[name].value = value;
+
+      let suggestions = [];
+      const ds100Result = getIBNRbyDS100(value.toUpperCase());
+
+      if (ds100Result !== null)
+        suggestions = await client.locations(ds100Result, { results: 1 });
+
+      suggestions = suggestions.concat(
+        await client.locations(value, { results: 10 }),
+      );
+
+      this.location[name].suggestionSelected = null;
+      this.location[name].suggestion = 0;
+      this.location[name].suggestions = suggestions;
+
+      this.requestUpdate();
+    }
+  };
+
+  changeHandler = (event) => {
+    const name = event.target.name;
+    const value = event.target.value;
+
+    if (name === "noTransfers") this.noTransfers = !this.noTransfers;
+    if (name === "isArrival") this.isArrival = !this.isArrival;
+    if (name === "dateTime") this.date.setDateTime(value);
+    if (name === "date") this.date.setDate(value);
+    if (name === "time") this.date.setTime(value);
+
+    this.requestUpdate();
+  };
 }
 
-customElements.define('search-view', SearchView);
+customElements.define("search-view", SearchView);
diff --git a/src/settings.js b/src/settings.js
@@ -1,59 +1,80 @@
-import { createStore } from 'zustand/vanilla';
-import { persist, createJSONStorage } from 'zustand/middleware'
-
-import { db, initDataStorage } from './dataStorage.js';
+import { upperFirst } from './helpers.js';
 import { getDefaultLanguage } from './translate.js';
 import { getDefaultProfile } from './hafasClient.js';
 
-export let   settingsState;
-export const settings = createStore()(
-	persist(
-		(set, get) => ({
-			language: getDefaultLanguage(),
-			profile: getDefaultProfile(),
-			products: {},
-			accessibility: 'none',
-			walkingSpeed: 'normal',
-			transferTime: 0,
-			loyaltyCard: 'NONE',
-			ageGroup: 'E',
-			journeysViewMode: 'canvas',
-			bikeFriendly: false,
-			combineDateTime: false,
-			showVia: false,
-			showPrices: true,
-			showDS100: true,
-
-			setJourneysViewMode: (journeysViewMode) => set({ journeysViewMode }),
-			setLanguage:         (language)         => set({ language }),
-			setProfile:          (profile)          => set({ profile }),
-			setTransferTime:     (transferTime)     => set({ transferTime }),
-			setAgeGroup:         (ageGroup)         => set({ ageGroup }),
-			setLoyaltyCard:      (loyaltyCard)      => set({ loyaltyCard }),
-			setAccessibility:    (accessibility)    => set({ accessibility }),
-			setWalkingSpeed:     (walkingSpeed)     => set({ walkingSpeed }),
-			setShowVia:          (showVia)          => set({ showVia }),
-
-			toggleCombineDateTime: ()    => set((state) => ({ combineDateTime: !state.combineDateTime })),
-			toggleShowPrices:      ()    => set((state) => ({ showPrices:      !state.showPrices })),
-			toggleShowDS100:       ()    => set((state) => ({ showDS100:       !state.showDS100 })),
-			toggleShowVia:         ()    => set((state) => ({ showVia:         !state.showVia })),
-			toggleBikeFriendly:    ()    => set((state) => ({ bikeFriendly:    !state.bikeFriendly })),
-			toggleProduct:         (key) => set((state) => {
-				state.products[key] = !state.products[key];
-				return { products: state.products };
-			})
-		}),
-		{
-			name: 'settings'
+let   state = {};
+const subscribers = new Set();
+const defaultSettings = {
+	language: getDefaultLanguage(),
+	profile: getDefaultProfile(),
+	products: {},
+	accessibility: 'none',
+	walkingSpeed: 'normal',
+	transferTime: 0,
+	loyaltyCard: 'NONE',
+	ageGroup: 'E',
+	journeysViewMode: 'canvas',
+	bikeFriendly: false,
+	combineDateTime: false,
+	showVia: false,
+	showPrices: true,
+	showDS100: true,
+};
+
+export const settings = {};
+export const initSettings = async () => {
+	let properties = {
+		subscribe: { value: callback => {
+			subscribers.add(callback);
+			return () => subscribers.delete(callback);
+		}},
+		toggleProduct: {
+			value: product => {
+				let products = state.products;
+				products[product] = !products[product];
+				localStorage[`products.${product}`] = JSON.stringify(products[product]);
+				state.products = products;
+			},
+		},
+	};
+
+	Object.keys(defaultSettings).forEach(key => {
+		if (typeof defaultSettings[key] === 'object') {
+			let prefix = `${key}.`;
+			state[key] = {};
+
+			Object.keys(localStorage)
+				.filter(element => element.startsWith(prefix))
+				.forEach(element => {
+					state[key][element.slice(prefix.length)] = JSON.parse(localStorage[element]);
+				});
+		} else {
+			state[key] = localStorage[key] ? JSON.parse(localStorage[key]) : defaultSettings[key];
 		}
-	)
-)
 
-export const initSettingsState = async () => {
-	settingsState = settings.getState();
+		properties[key] = {
+			enumerable: true,
+			get: () => state[key],
+			set: newValue => {
+				state[key] = newValue;
+				if (typeof newValue === 'object') {
+					Object.keys(newValue).forEach(objKey => {
+						localStorage[`${key}.${objKey}`] = JSON.stringify(newValue[objKey]);
+					});
+				} else {
+					localStorage[key] = JSON.stringify(newValue);
+				}
+				subscribers.forEach(callback => callback(settings));
+			},
+		};
+
+		properties[`set${upperFirst(key)}`] = {
+			value: newValue => { settings[key] = newValue; },
+		};
 
-	settings.subscribe(state => {
-		settingsState = state;
+		if (typeof defaultSettings[key] === 'boolean')
+			properties[`toggle${upperFirst(key)}`] = { value: () => { settings[key] = !settings[key]; } };
 	});
+
+	Object.defineProperties(settings, properties);
 };
diff --git a/src/templates.js b/src/templates.js
@@ -1,7 +1,7 @@
 import { html, css, nothing } from 'lit';
 import { unsafeHTML } from 'lit/directives/unsafe-html.js';
 
-import { settingsState } from './settings.js';
+import { settings } from './settings.js';
 import { getDS100byIBNR } from './ds100.js';
 import { CustomDate } from './helpers.js';
 

@@ -33,7 +33,7 @@ export const remarksModal = (element, remarks) => element.showDialogOverlay('rem
 
 export const stopTemplate = (profile, stop) => {
 	let stopName = stop.name;
-	if (settingsState.showDS100) {
+	if (settings.showDS100) {
 		const ds100 = getDS100byIBNR(stop.id);
 		if (ds100 !== null) stopName += ` (${ds100})`;
 	}
diff --git a/src/translate.js b/src/translate.js
@@ -1,4 +1,4 @@
-import { settingsState } from './settings.js';
+import { settings } from './settings.js';
 import { languages } from './languages.js';
 
 export const getDefaultLanguage = () => {

@@ -12,7 +12,7 @@ export const getDefaultLanguage = () => {
 export const getLanguages = () => Object.keys(languages);
 
 export const t = (key, ...params) => {
-	let translation = languages[settingsState.language][key];
+	let translation = languages[settings.language][key];
 
 	if (!translation) translation = languages['en'][key]
 	if (!translation) return key;