simplify application and remove window references
Sonarqube Scanner / Build and analyze (push) Failing after 1m27s

This commit is contained in:
Florian Zumpe
2026-06-17 16:08:01 +02:00
parent 6a4d84ac6a
commit 32f161483d
20 changed files with 560 additions and 282 deletions
+37 -17
View File
@@ -99,7 +99,7 @@ button,
padding: .35rem;
border-color: rgba(255, 255, 255, .45);
border-radius: .45rem;
background: rgba(255, 255, 255, .12);
background: var(--primary-dark);
color: #fff;
}
@@ -289,38 +289,45 @@ button,
color: var(--muted);
}
.route-facts {
#route-page dl {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: .5rem;
margin: 1rem 0;
}
.route-facts div {
#route-page dl > div {
padding: .75rem;
border-radius: .5rem;
background: var(--surface);
text-align: center;
}
.route-facts dt {
#route-page dt {
color: var(--muted);
font-size: .8rem;
}
.route-facts dd {
#route-page dd {
margin: .25rem 0 0;
font-weight: 700;
}
.route-controls {
#route-page fieldset,
#slideshow > fieldset {
min-width: 0;
border: 0;
}
#route-page fieldset {
display: grid;
padding: 0;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: .5rem;
margin: 0 0 1rem;
}
.route-controls button {
#route-page fieldset button {
display: grid;
place-items: center;
min-width: 0;
@@ -334,23 +341,35 @@ button,
touch-action: manipulation;
}
.route-controls button:focus-visible {
#route-page fieldset button:focus-visible {
outline: .2rem solid var(--focus);
outline-offset: .15rem;
}
.route-controls button:disabled {
#route-page fieldset button:disabled {
cursor: default;
filter: grayscale(.65);
opacity: .42;
}
.route-controls img {
#route-page fieldset img {
width: 2rem;
height: 2rem;
pointer-events: none;
}
#route-page fieldset > legend,
#slideshow > fieldset > legend {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
#start-route {
background: var(--primary);
border-color: var(--primary-dark);
@@ -386,8 +405,9 @@ button,
background: #111;
}
#slide-controls {
#slideshow > fieldset {
display: grid;
margin: 0;
grid-template-columns: var(--icon-button-size) minmax(0, 1fr) var(--icon-button-size);
align-items: stretch;
gap: .5rem;
@@ -396,7 +416,7 @@ button,
padding: .65rem;
}
#slide-controls .ui-button {
#slideshow > fieldset .ui-button {
display: grid;
place-items: center;
min-width: 0;
@@ -405,13 +425,13 @@ button,
padding: .45rem;
}
#slide-controls .ui-button img {
#slideshow > fieldset .ui-button img {
width: 1.2rem;
height: 1.2rem;
pointer-events: none;
}
.slide-meta {
#slideshow > fieldset > div {
display: flex;
min-width: 0;
min-height: var(--icon-button-size);
@@ -521,17 +541,17 @@ button,
grid-template-columns: minmax(0, 1fr) 3rem;
}
.route-facts {
#route-page dl {
gap: .35rem;
}
.route-facts div {
#route-page dl > div {
padding: .6rem .25rem;
}
}
@media (max-width: 22rem) {
.route-facts {
#route-page dl {
grid-template-columns: 1fr;
}
}
+17 -15
View File
@@ -8,8 +8,8 @@
<link rel="stylesheet" href="css/bootstrap.css">
</head>
<body>
<div id="bootstrap-screen" role="status" aria-live="polite">
<section aria-label="Anwendung wird initialisiert">
<div id="bootstrap-screen" aria-live="polite" aria-busy="true">
<section>
<span aria-hidden="true">🧭</span>
<h1>Wegwichtel</h1>
<p>Der Wegwichtel bereitet Routen und Medien vor.</p>
@@ -31,20 +31,20 @@
</div>
<main id="app-shell" hidden>
<section id="routes-page" class="app-page" aria-label="Routenauswahl">
<section id="routes-page" class="app-page">
<header>
<h1>Wegwichtel</h1>
</header>
<div class="content">
<p id="location-hint" class="notice">Standort wird ermittelt.</p>
<p id="location-hint" class="notice">Standort wird für nahe Routen lokal auf diesem Gerät ermittelt.</p>
<section aria-label="Routen in der Nähe">
<section>
<h2>Routen in deiner Nähe</h2>
<ul id="nearby-route-list" class="route-list" aria-live="polite"></ul>
</section>
<section aria-label="Alle Routen">
<section>
<h2>Alle Routen</h2>
<label for="route-search">Routen durchsuchen</label>
@@ -64,7 +64,7 @@
</div>
</section>
<section id="route-page" class="app-page" aria-label="Ausgewählte Route" hidden>
<section id="route-page" class="app-page" hidden>
<header>
<button class="back-button" type="button" data-page-target="#routes-page" aria-label="Zurück zur Routenauswahl" title="Zurück zur Routenauswahl">
<img src="images/icons/back.svg" alt="">
@@ -75,13 +75,14 @@
<div class="content">
<p id="route-description"></p>
<dl class="route-facts">
<dl>
<div><dt>Länge</dt><dd id="route-distance">–</dd></div>
<div><dt>Höhenmeter</dt><dd id="route-elevation">–</dd></div>
<div><dt>POIs</dt><dd id="route-poi-count">–</dd></div>
</dl>
<div class="route-controls" role="group" aria-label="Routensteuerung">
<fieldset>
<legend>Routensteuerung</legend>
<button id="start-route" type="button" aria-label="Route starten" title="Route starten">
<img src="images/icons/play.svg" alt="">
</button>
@@ -91,7 +92,7 @@
<button id="stop-route" type="button" aria-label="Route beenden" title="Route beenden" disabled>
<img src="images/icons/stop.svg" alt="">
</button>
</div>
</fieldset>
<p id="tracking-status" class="notice" hidden></p>
<h2>Stationen</h2>
@@ -99,7 +100,7 @@
</div>
</section>
<section id="poi-page" class="app-page" aria-label="Station" hidden>
<section id="poi-page" class="app-page" hidden>
<header>
<button class="back-button" type="button" data-page-target="#route-page" aria-label="Zurück zur Route" title="Zurück zur Route">
<img src="images/icons/back.svg" alt="">
@@ -113,18 +114,19 @@
<img id="slide-image" alt="">
</figure>
<div id="slide-controls" role="group" aria-label="Bildnavigation">
<fieldset>
<legend>Bildnavigation</legend>
<button id="slide-prev" type="button" aria-label="Vorheriges Bild" title="Vorheriges Bild">
<img src="images/icons/chevron-left.svg" alt="">
</button>
<div class="slide-meta">
<div>
<p id="slide-caption"></p>
<span id="slide-position" aria-live="polite">0 / 0</span>
<span id="slide-position">0 / 0</span>
</div>
<button id="slide-next" type="button" aria-label="Nächstes Bild" title="Nächstes Bild">
<img src="images/icons/chevron-right.svg" alt="">
</button>
</div>
</fieldset>
</div>
<p id="poi-description"></p>
+1 -1
View File
@@ -8,4 +8,4 @@
routes: position => request('/routes' + (position ? `?lat=${encodeURIComponent(position.lat)}&lon=${encodeURIComponent(position.lon)}&radiusKm=${ns.Config.routeRadiusKm}` : '')),
route: id => request(`/routes/${encodeURIComponent(id)}`)
};
}(window.Wegwichtel, window.jQuery));
}(globalThis.Wegwichtel, globalThis.jQuery));
+11 -14
View File
@@ -9,7 +9,6 @@
routeState: 'idle',
activePage: null,
routeProgressIndex: null,
activePoiId: null,
deviceHeading: null
};
@@ -21,7 +20,7 @@
$(document)
.on('pointerup', selector, function (event) {
const pointer = event.originalEvent;
if (pointer && pointer.isPrimary === false) return;
if (pointer?.isPrimary === false) return;
if (pointer?.pointerType === 'mouse' && pointer.button !== 0) return;
event.preventDefault();
handler.call(this, event);
@@ -38,10 +37,10 @@
if (!target.length) throw new Error(`Unbekannte Ansicht: ${pageSelector}`);
const previous = state.activePage;
$('.app-page').attr('hidden', true).removeClass('is-active');
target.removeAttr('hidden').addClass('is-active');
$('.app-page').attr('hidden', true);
target.removeAttr('hidden');
state.activePage = pageSelector;
window.scrollTo({ top: 0, behavior: options.instant ? 'auto' : 'smooth' });
globalThis.scrollTo({ top: 0, behavior: options.instant ? 'auto' : 'smooth' });
if (options.updateHistory === false) return;
const historyState = { page: pageSelector, previous };
@@ -52,7 +51,7 @@
function normalizeSearch(value) {
return String(value ?? '')
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.replaceAll(/[\u0300-\u036f]/g, '')
.toLocaleLowerCase('de');
}
@@ -172,11 +171,10 @@
}
function setActivePoi(poiId) {
state.activePoiId = poiId == null ? null : Number(poiId);
$('#poi-list [data-poi-id]').removeAttr('aria-current');
if (state.activePoiId != null) {
$(`#poi-list [data-poi-id="${state.activePoiId}"]`).attr('aria-current', 'step');
}
if (poiId == null) return;
$(`#poi-list [data-poi-id="${Number(poiId)}"]`).attr('aria-current', 'step');
}
function endRoute({ announce = true, returnToRoutes = false } = {}) {
@@ -451,7 +449,7 @@
const metrics = updatePoiIndicators(position);
if (state.routeState !== 'running') return;
let nearest = nearestPendingPoi(metrics);
const nearest = nearestPendingPoi(metrics);
updateNavigation(position);
$('#tracking-status').text(
@@ -463,7 +461,6 @@
state.triggered.add(nearest.poi.id);
ns.Vibration.start(ns.Vibration.Patterns.ACTIVE_POI);
showPoi(nearest.poi);
nearest = nearestPendingPoi(metrics);
updateNavigation(position);
}
}
@@ -545,7 +542,7 @@
endRoute({ returnToRoutes: true });
});
window.addEventListener('popstate', event => {
globalThis.addEventListener('popstate', event => {
const page = event.state?.page || '#routes-page';
navigate(page, { updateHistory: false, instant: true });
});
@@ -587,4 +584,4 @@
hideNavigation();
}
};
}(window.Wegwichtel, window.jQuery));
}(globalThis.Wegwichtel, globalThis.jQuery));
+1 -1
View File
@@ -36,4 +36,4 @@
unload
};
}(window.Wegwichtel, window.jQuery));
}(globalThis.Wegwichtel, globalThis.jQuery));
+6 -6
View File
@@ -41,7 +41,7 @@
}
function initializeJqueryUi() {
const $ = window.jQuery;
const $ = globalThis.jQuery;
$('#bootstrap-retry').button({ icon: 'ui-icon-refresh' });
$('.back-button').button();
$('#clear-route-search').button({ icon: 'ui-icon-close', showLabel: false });
@@ -56,7 +56,7 @@
try {
await loadScript(
`vendor/jquery/jquery-${versions.jquery}.min.js`,
() => window.jQuery?.fn?.jquery === versions.jquery
() => globalThis.jQuery?.fn?.jquery === versions.jquery
);
mark('jquery', 'done');
@@ -64,20 +64,20 @@
await loadStyle('css/app.css');
await loadScript(
`vendor/jquery-ui/jquery-ui-${versions.jqueryUi}.min.js`,
() => window.jQuery?.ui?.version === versions.jqueryUi
() => globalThis.jQuery?.ui?.version === versions.jqueryUi
);
initializeJqueryUi();
mark('jquery-ui', 'done');
for (const module of ['config', 'api-client', 'distance', 'geolocation', 'orientation', 'slideshow', 'audio-player', 'vibration', 'app']) {
await loadScript(`js/${module}.js`, () => Boolean(window.Wegwichtel));
await loadScript(`js/${module}.js`, () => Boolean(globalThis.Wegwichtel));
}
mark('modules', 'done');
await window.Wegwichtel.App.initialize({ markStep: mark });
await globalThis.Wegwichtel.App.initialize({ markStep: mark });
document.getElementById('bootstrap-screen').hidden = true;
document.getElementById('app-shell').hidden = false;
window.Wegwichtel.App.navigate('#routes-page', { replace: true });
globalThis.Wegwichtel.App.navigate('#routes-page', { replace: true });
} catch (error) {
console.error(error);
const current = steps.find(step => !document.querySelector(`[data-step="${step}"]`)?.classList.contains('done'));
+2 -2
View File
@@ -1,2 +1,2 @@
window.Wegwichtel = window.Wegwichtel || {};
window.Wegwichtel.Config = Object.freeze({ apiBase: '/api', routeRadiusKm: 25, geolocationTimeoutMs: 12000 });
globalThis.Wegwichtel ??= {};
globalThis.Wegwichtel.Config = Object.freeze({ apiBase: '/api', routeRadiusKm: 25, geolocationTimeoutMs: 12000 });
+1 -1
View File
@@ -30,4 +30,4 @@
return meters < 1000 ? `${Math.round(meters)} m` : `${(meters / 1000).toFixed(1)} km`;
}
};
}(window.Wegwichtel));
}(globalThis.Wegwichtel));
+26 -14
View File
@@ -1,8 +1,17 @@
(function (ns) {
'use strict';
const WATCH_OPTIONS = Object.freeze({
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 5000
});
let watchId = null;
function geolocation() {
return globalThis.navigator?.geolocation;
}
function normalize(position) {
return {
lat: position.coords.latitude,
@@ -15,19 +24,24 @@
};
}
function unsupportedError() {
return new Error('Dieses Gerät unterstützt keine Geolokalisierung.');
}
ns.Geo = {
current() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Dieses Gerät unterstützt keine Geolokalisierung.'));
const service = geolocation();
if (!service) {
reject(unsupportedError());
return;
}
navigator.geolocation.getCurrentPosition(
service.getCurrentPosition(
position => resolve(normalize(position)),
reject,
{
enableHighAccuracy: true,
...WATCH_OPTIONS,
timeout: ns.Config.geolocationTimeoutMs,
maximumAge: 30000
}
@@ -37,25 +51,23 @@
start(callback, onError) {
this.stop();
if (!navigator.geolocation) {
onError?.(new Error('Dieses Gerät unterstützt keine Geolokalisierung.'));
const service = geolocation();
if (!service) {
onError?.(unsupportedError());
return;
}
watchId = navigator.geolocation.watchPosition(
watchId = service.watchPosition(
position => callback(normalize(position)),
onError,
{
enableHighAccuracy: true,
timeout: 20000,
maximumAge: 5000
}
WATCH_OPTIONS
);
},
stop() {
if (watchId != null) navigator.geolocation.clearWatch(watchId);
const service = geolocation();
if (watchId != null) service?.clearWatch(watchId);
watchId = null;
}
};
}(window.Wegwichtel));
}(globalThis.Wegwichtel));
+4 -4
View File
@@ -22,7 +22,7 @@
}
async function requestPermission() {
const orientation = window.DeviceOrientationEvent;
const orientation = globalThis.DeviceOrientationEvent;
if (!orientation) return false;
if (typeof orientation.requestPermission === 'function') {
@@ -51,14 +51,14 @@
if (heading != null) callback?.(heading);
};
eventNames.forEach(name => window.addEventListener(name, listener, true));
eventNames.forEach(name => globalThis.addEventListener(name, listener, true));
return true;
},
stop() {
if (listener) eventNames.forEach(name => window.removeEventListener(name, listener, true));
if (listener) eventNames.forEach(name => globalThis.removeEventListener(name, listener, true));
listener = null;
callback = null;
}
};
}(window.Wegwichtel));
}(globalThis.Wegwichtel));
+2 -3
View File
@@ -9,7 +9,7 @@
$(document)
.on('pointerup', selector, function (event) {
const pointer = event.originalEvent;
if (pointer && pointer.isPrimary === false) return;
if (pointer?.isPrimary === false) return;
if (pointer?.pointerType === 'mouse' && pointer.button !== 0) return;
event.preventDefault();
handler.call(this, event);
@@ -29,7 +29,6 @@
function render() {
const hasImages = images.length > 0;
$('#slideshow').toggle(hasImages);
$('#slide-controls').attr('data-image-count', images.length);
if (!hasImages) return;
const image = images[index];
@@ -86,4 +85,4 @@
if (deltaX < 0) ns.Slideshow.next();
else ns.Slideshow.previous();
});
}(window.Wegwichtel, window.jQuery));
}(globalThis.Wegwichtel, globalThis.jQuery));
+2 -2
View File
@@ -30,7 +30,7 @@
if (!Number.isFinite(duration) || duration < 0) {
throw new TypeError('Die Vibrationsdauer muss eine nichtnegative Millisekundenangabe sein.');
}
return duration;
return [duration];
}
function start(pattern = Patterns.LONG) {
@@ -50,4 +50,4 @@
start,
stop
});
}(window.Wegwichtel));
}(globalThis.Wegwichtel));