update editor and add tests
This commit is contained in:
+215
-69
@@ -3,6 +3,8 @@
|
||||
|
||||
const state = {
|
||||
routes: [],
|
||||
localRoutes: [],
|
||||
localRoutesError: null,
|
||||
route: null,
|
||||
position: null,
|
||||
triggered: new Set(),
|
||||
@@ -20,7 +22,6 @@
|
||||
return $('<div>').text(value ?? '').html();
|
||||
}
|
||||
|
||||
|
||||
function persistRouteProgress() {
|
||||
if (!state.route) return;
|
||||
if (state.persistTimer != null) {
|
||||
@@ -54,7 +55,6 @@
|
||||
state.routeState = saved?.routeState === 'running' || saved?.routeState === 'paused'
|
||||
? 'paused'
|
||||
: 'idle';
|
||||
|
||||
if (state.routeState === 'paused') {
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
@@ -89,7 +89,6 @@
|
||||
target.removeAttr('hidden');
|
||||
state.activePage = pageSelector;
|
||||
globalThis.scrollTo({ top: 0, behavior: options.instant ? 'auto' : 'smooth' });
|
||||
|
||||
if (options.updateHistory === false) return;
|
||||
const historyState = { page: pageSelector, previous };
|
||||
if (options.replace) history.replaceState(historyState, '', pageSelector);
|
||||
@@ -112,31 +111,99 @@
|
||||
].filter(Boolean).join(' ')).includes(searchTerm);
|
||||
}
|
||||
|
||||
function renderRouteItems(listSelector, routes, emptyText) {
|
||||
function routeButton(route, source) {
|
||||
const proximity = route.proximityM == null
|
||||
? ''
|
||||
: `<small>${escapeHtml(ns.Distance.format(route.proximityM))} entfernt</small>`;
|
||||
const sourceLabel = source === 'local' ? '<small>Lokaler Entwurf</small>' : '';
|
||||
return `
|
||||
<li>
|
||||
<button type="button" data-route-select data-route-id="${escapeHtml(route.id)}" data-route-source="${source}">
|
||||
<strong>${escapeHtml(route.name)}</strong>
|
||||
<span>${escapeHtml(route.schoolName || route.description || '')}</span>
|
||||
${sourceLabel}
|
||||
${proximity}
|
||||
</button>
|
||||
</li>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderRouteItems(listSelector, routes, emptyText, source = 'server') {
|
||||
const list = $(listSelector).empty();
|
||||
const items = Array.isArray(routes) ? routes.filter(Boolean) : [];
|
||||
if (!items.length) {
|
||||
list.append(`<li><p>${escapeHtml(emptyText)}</p></li>`);
|
||||
return;
|
||||
}
|
||||
items.forEach(route => list.append(routeButton(route, source)));
|
||||
}
|
||||
|
||||
items.forEach(route => {
|
||||
const proximity = route.proximityM == null
|
||||
? ''
|
||||
: `<small>${escapeHtml(ns.Distance.format(route.proximityM))} entfernt</small>`;
|
||||
function nextPoiMetric() {
|
||||
const pois = state.route?.pois || [];
|
||||
const pending = pois
|
||||
.filter(poi => !state.triggered.has(poi.id))
|
||||
.slice()
|
||||
.sort((left, right) => Number(left.sequence ?? 0) - Number(right.sequence ?? 0));
|
||||
if (!pending.length) return null;
|
||||
if (!state.position) return { poi: pending[0], distance: null };
|
||||
return nearestPendingPoi(poiMetrics(state.position));
|
||||
}
|
||||
|
||||
function updateRouteControlSummary() {
|
||||
const title = $('#route-control-title');
|
||||
const next = $('#route-control-next');
|
||||
if (!state.route) {
|
||||
title.text('Route wählen');
|
||||
next.text('Aktive und lokale Strecken');
|
||||
return;
|
||||
}
|
||||
|
||||
title.text(state.route.name || 'Route');
|
||||
const metric = nextPoiMetric();
|
||||
if (!metric) {
|
||||
next.text((state.route.pois || []).length ? 'Alle POIs erreicht' : 'Keine POIs vorhanden');
|
||||
return;
|
||||
}
|
||||
const distance = metric.distance == null ? '' : ` · ${Math.round(metric.distance)} m`;
|
||||
next.text(`Nächster POI: ${metric.poi.title}${distance}`);
|
||||
}
|
||||
|
||||
function renderRouteControlList(selector, routes, source, emptyText) {
|
||||
const list = $(selector).empty();
|
||||
if (!routes.length) {
|
||||
list.append(`<li><span>${escapeHtml(emptyText)}</span></li>`);
|
||||
return;
|
||||
}
|
||||
routes.forEach(route => {
|
||||
const current = state.route && String(state.route.id) === String(route.id)
|
||||
&& (state.route.source || 'server') === source;
|
||||
list.append(`
|
||||
<li>
|
||||
<button type="button" data-route-id="${route.id}">
|
||||
<button type="button" data-route-select data-route-id="${escapeHtml(route.id)}" data-route-source="${source}" ${current ? 'aria-current="true"' : ''}>
|
||||
<strong>${escapeHtml(route.name)}</strong>
|
||||
<span>${escapeHtml(route.schoolName || route.description || '')}</span>
|
||||
${proximity}
|
||||
<small>${source === 'local' ? 'Lokaler Entwurf' : 'Veröffentlicht'}</small>
|
||||
</button>
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
function renderRouteControl() {
|
||||
updateRouteControlSummary();
|
||||
renderRouteControlList(
|
||||
'#route-control-server-list',
|
||||
state.routes,
|
||||
'server',
|
||||
'Keine veröffentlichte Strecke vorhanden.'
|
||||
);
|
||||
renderRouteControlList(
|
||||
'#route-control-local-list',
|
||||
state.localRoutes,
|
||||
'local',
|
||||
state.localRoutesError || 'Noch keine lokale Strecke vorhanden.'
|
||||
);
|
||||
}
|
||||
|
||||
function renderRoutes() {
|
||||
const routes = Array.isArray(state.routes) ? state.routes.filter(Boolean) : [];
|
||||
const radiusM = ns.Config.routeRadiusKm * 1000;
|
||||
@@ -152,6 +219,9 @@
|
||||
.filter(route => routeMatchesSearch(route, searchTerm))
|
||||
.slice()
|
||||
.sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
|
||||
const localRoutes = state.localRoutes
|
||||
.slice()
|
||||
.sort((a, b) => String(a.name ?? '').localeCompare(String(b.name ?? ''), 'de'));
|
||||
|
||||
const nearbyEmptyText = state.position
|
||||
? `Keine Route im Umkreis von ${ns.Config.routeRadiusKm} km gefunden. Wähle unten eine beliebige Route aus.`
|
||||
@@ -161,20 +231,26 @@
|
||||
renderRouteItems(
|
||||
'#all-route-list',
|
||||
allRoutes,
|
||||
searchTerm ? 'Keine Route entspricht dem Suchbegriff.' : 'Es sind noch keine Routen vorhanden.'
|
||||
searchTerm ? 'Keine Route entspricht dem Suchbegriff.' : 'Es sind noch keine veröffentlichten Routen vorhanden.'
|
||||
);
|
||||
renderRouteItems(
|
||||
'#local-route-list',
|
||||
localRoutes,
|
||||
state.localRoutesError || 'Noch keine lokalen Strecken gespeichert.',
|
||||
'local'
|
||||
);
|
||||
|
||||
$('#all-routes-summary').text(
|
||||
searchTerm
|
||||
? `${allRoutes.length} von ${routes.length} Routen gefunden.`
|
||||
: `${routes.length} Routen verfügbar.`
|
||||
? `${allRoutes.length} von ${routes.length} veröffentlichten Routen gefunden.`
|
||||
: `${routes.length} veröffentlichte Routen verfügbar.`
|
||||
);
|
||||
renderRouteControl();
|
||||
}
|
||||
|
||||
function initializeRouteSearch() {
|
||||
const input = $('#route-search');
|
||||
if (!input.length || typeof input.autocomplete !== 'function') return;
|
||||
|
||||
input.autocomplete({
|
||||
minLength: 0,
|
||||
source(request, response) {
|
||||
@@ -215,7 +291,6 @@
|
||||
const running = state.routeState === 'running';
|
||||
const paused = state.routeState === 'paused';
|
||||
const startLabel = paused ? 'Route fortsetzen' : 'Route starten';
|
||||
|
||||
$('#start-route')
|
||||
.prop('disabled', running)
|
||||
.attr({ 'aria-label': startLabel, title: startLabel });
|
||||
@@ -226,7 +301,6 @@
|
||||
function setActivePoi(poiId) {
|
||||
$('#poi-list [data-poi-id]').removeAttr('aria-current');
|
||||
if (poiId == null) return;
|
||||
|
||||
$(`#poi-list [data-poi-id="${Number(poiId)}"]`).attr('aria-current', 'step');
|
||||
}
|
||||
|
||||
@@ -243,7 +317,6 @@
|
||||
ns.AudioPlayer.unload();
|
||||
setActivePoi(null);
|
||||
hideNavigation();
|
||||
|
||||
if (announce && wasActive && !returnToRoutes) {
|
||||
$('#tracking-status').prop('hidden', false).text('Route beendet.');
|
||||
} else {
|
||||
@@ -251,16 +324,18 @@
|
||||
}
|
||||
|
||||
updateRouteControls();
|
||||
|
||||
if (returnToRoutes) {
|
||||
state.route = null;
|
||||
updateRouteControlSummary();
|
||||
renderRouteControl();
|
||||
navigate('#routes-page', { replace: true });
|
||||
} else {
|
||||
updateRouteControlSummary();
|
||||
}
|
||||
}
|
||||
|
||||
function pauseRoute() {
|
||||
if (state.routeState !== 'running') return;
|
||||
|
||||
state.routeState = 'paused';
|
||||
persistRouteProgress();
|
||||
stopRouteSensors();
|
||||
@@ -270,32 +345,37 @@
|
||||
.text('Route pausiert. Die Standortverfolgung ist angehalten.');
|
||||
showNavigation('Route pausiert', $('#navigation-distance').text() || '–');
|
||||
updateRouteControls();
|
||||
updateRouteControlSummary();
|
||||
}
|
||||
|
||||
function renderRoute(route) {
|
||||
if (state.routeState !== 'idle') endRoute({ announce: false });
|
||||
|
||||
state.route = route;
|
||||
function suspendCurrentRoute() {
|
||||
if (!state.route) return;
|
||||
if (state.routeState === 'running') state.routeState = 'paused';
|
||||
persistRouteProgress();
|
||||
stopRouteSensors();
|
||||
ns.AudioPlayer.unload();
|
||||
state.activePoiId = null;
|
||||
restoreRouteProgress(route);
|
||||
setActivePoi(null);
|
||||
hideNavigation();
|
||||
}
|
||||
|
||||
function renderRouteView(route) {
|
||||
$('#route-title').text(route.name);
|
||||
$('#route-description').text(route.description || '');
|
||||
$('#route-distance').text(ns.Distance.format(route.distanceM));
|
||||
$('#route-elevation').text(`${Math.round(route.elevationGainM)} m`);
|
||||
$('#route-elevation').text(`${Math.round(route.elevationGainM || 0)} m`);
|
||||
$('#route-poi-count').text(route.pois.length);
|
||||
updateRouteControls();
|
||||
|
||||
const list = $('#poi-list').empty();
|
||||
if (!route.pois.length) {
|
||||
list.append('<li><p>Diese Route enthält noch keine Stationen.</p></li>');
|
||||
updateRouteControlSummary();
|
||||
return;
|
||||
}
|
||||
|
||||
route.pois.forEach(poi => {
|
||||
list.append(`
|
||||
<li>
|
||||
<button type="button" data-poi-id="${poi.id}">
|
||||
<button type="button" data-poi-select data-poi-id="${poi.id}">
|
||||
<strong>${escapeHtml(poi.title)}</strong>
|
||||
<span>${escapeHtml(poi.description || '')}</span>
|
||||
<span class="poi-proximity" aria-label="Entfernung wird ermittelt">
|
||||
@@ -308,8 +388,18 @@
|
||||
</li>
|
||||
`);
|
||||
});
|
||||
|
||||
updatePoiIndicators(state.position);
|
||||
updateRouteControlSummary();
|
||||
}
|
||||
|
||||
function renderRoute(route) {
|
||||
if (state.route) suspendCurrentRoute();
|
||||
state.route = route;
|
||||
state.activePoiId = null;
|
||||
restoreRouteProgress(route);
|
||||
setActivePoi(null);
|
||||
renderRouteView(route);
|
||||
renderRouteControl();
|
||||
}
|
||||
|
||||
function showPoi(poi) {
|
||||
@@ -334,7 +424,6 @@
|
||||
start = Math.max(0, state.routeProgressIndex - 3);
|
||||
end = Math.min(points.length, state.routeProgressIndex + 201);
|
||||
}
|
||||
|
||||
let nearestIndex = start;
|
||||
let nearestDistance = Number.POSITIVE_INFINITY;
|
||||
for (let index = start; index < end; index += 1) {
|
||||
@@ -344,7 +433,6 @@
|
||||
nearestIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
return nearestIndex;
|
||||
}
|
||||
|
||||
@@ -360,7 +448,6 @@
|
||||
function poiMetrics(position) {
|
||||
if (!position || !state.route?.pois?.length) return [];
|
||||
const heading = effectiveHeading(position);
|
||||
|
||||
return state.route.pois.map(poi => {
|
||||
const distance = ns.Distance.meters(position, poi);
|
||||
const bearing = ns.Distance.bearing(position, poi);
|
||||
@@ -377,7 +464,6 @@
|
||||
|
||||
function updatePoiIndicators(position) {
|
||||
const metrics = poiMetrics(position);
|
||||
|
||||
metrics.forEach(metric => {
|
||||
const indicator = $(`#poi-list [data-poi-id="${metric.poi.id}"] .poi-proximity`);
|
||||
const distance = Math.round(metric.distance);
|
||||
@@ -387,7 +473,6 @@
|
||||
indicator.find('svg').css('transform', `rotate(${metric.rotation.toFixed(1)}deg)`);
|
||||
indicator.find('small').text(`${distance} m`);
|
||||
});
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
@@ -403,20 +488,17 @@
|
||||
const previousProgressIndex = state.routeProgressIndex;
|
||||
const points = state.route?.points || [];
|
||||
if (!points.length) return null;
|
||||
|
||||
const nearestIndex = nearestRoutePointIndex(position);
|
||||
if (nearestIndex == null) return null;
|
||||
|
||||
state.routeProgressIndex = state.routeProgressIndex == null
|
||||
? nearestIndex
|
||||
: Math.max(state.routeProgressIndex, nearestIndex);
|
||||
|
||||
const lastIndex = points.length - 1;
|
||||
let targetIndex = Math.min(lastIndex, state.routeProgressIndex + 1);
|
||||
let target = points[targetIndex];
|
||||
let distance = ns.Distance.meters(position, target);
|
||||
const reachedRadius = Math.max(4, Math.min(12, Math.round(position.accuracy || 4)));
|
||||
|
||||
while (targetIndex < lastIndex && distance <= reachedRadius) {
|
||||
state.routeProgressIndex = targetIndex;
|
||||
targetIndex += 1;
|
||||
@@ -429,7 +511,6 @@
|
||||
if (state.routeProgressIndex !== previousProgressIndex) scheduleRouteProgressPersist();
|
||||
return { reached: true, target, distance: 0 };
|
||||
}
|
||||
|
||||
if (state.routeProgressIndex !== previousProgressIndex) scheduleRouteProgressPersist();
|
||||
return { reached: false, target, distance };
|
||||
}
|
||||
@@ -437,7 +518,6 @@
|
||||
function showPoiNavigation(position, metric) {
|
||||
const heading = effectiveHeading(position);
|
||||
const distance = Math.round(metric.distance);
|
||||
|
||||
$('#navigation-arrow').css('transform', `rotate(${metric.rotation.toFixed(1)}deg)`);
|
||||
showNavigation(
|
||||
heading == null
|
||||
@@ -464,14 +544,12 @@
|
||||
$('#navigation-footer').attr('aria-label', 'Alle Stationen und das Routenziel erreicht');
|
||||
return;
|
||||
}
|
||||
|
||||
const bearing = ns.Distance.bearing(position, progress.target);
|
||||
const heading = effectiveHeading(position);
|
||||
const rotation = heading == null
|
||||
? bearing
|
||||
: ns.Distance.normalizeDegrees(bearing - heading);
|
||||
const distance = Math.round(progress.distance);
|
||||
|
||||
$('#navigation-arrow').css('transform', `rotate(${rotation.toFixed(1)}deg)`);
|
||||
showNavigation(
|
||||
heading == null ? 'Norden oben · nächster Routenpunkt' : 'Nächster Routenpunkt',
|
||||
@@ -485,11 +563,9 @@
|
||||
|
||||
function updateNavigation(position) {
|
||||
if (state.routeState !== 'running') return;
|
||||
|
||||
const progress = updateRouteProgress(position);
|
||||
const metrics = updatePoiIndicators(position);
|
||||
const nextPoi = nearestPendingPoi(metrics);
|
||||
|
||||
if (nextPoi) showPoiNavigation(position, nextPoi);
|
||||
else showRoutePointNavigation(position, progress);
|
||||
}
|
||||
@@ -501,37 +577,36 @@
|
||||
const delta = ((heading - state.deviceHeading + 540) % 360) - 180;
|
||||
state.deviceHeading = ns.Distance.normalizeDegrees(state.deviceHeading + delta * 0.25);
|
||||
}
|
||||
|
||||
if (state.position) updateNavigation(state.position);
|
||||
}
|
||||
|
||||
function evaluatePosition(position) {
|
||||
state.position = position;
|
||||
updateRouteProximity(position);
|
||||
renderRouteControl();
|
||||
if (!state.route) return;
|
||||
|
||||
const metrics = updatePoiIndicators(position);
|
||||
updateRouteControlSummary();
|
||||
if (state.routeState !== 'running') return;
|
||||
|
||||
const nearest = nearestPendingPoi(metrics);
|
||||
updateNavigation(position);
|
||||
|
||||
$('#tracking-status').text(
|
||||
`GPS aktiv · Genauigkeit ${Math.round(position.accuracy)} m`
|
||||
+ (nearest ? ` · nächster POI ${Math.round(nearest.distance)} m` : ' · alle POIs erreicht')
|
||||
);
|
||||
|
||||
if (nearest && nearest.distance <= nearest.poi.triggerRadiusM) {
|
||||
state.triggered.add(nearest.poi.id);
|
||||
persistRouteProgress();
|
||||
ns.Vibration.start(ns.Vibration.Patterns.ACTIVE_POI);
|
||||
showPoi(nearest.poi);
|
||||
updateNavigation(position);
|
||||
updateRouteControlSummary();
|
||||
}
|
||||
}
|
||||
|
||||
async function startRoute() {
|
||||
if (!state.route || state.routeState === 'running') return;
|
||||
|
||||
const resumed = state.routeState === 'paused';
|
||||
if (!resumed) {
|
||||
state.routeProgressIndex = null;
|
||||
@@ -542,11 +617,11 @@
|
||||
state.deviceHeading = null;
|
||||
persistRouteProgress();
|
||||
updateRouteControls();
|
||||
updateRouteControlSummary();
|
||||
$('#tracking-status')
|
||||
.prop('hidden', false)
|
||||
.text(resumed ? 'Route wird fortgesetzt.' : 'Route wird gestartet. GPS wird aktiviert.');
|
||||
showNavigation(resumed ? 'Route wird fortgesetzt' : 'Position wird ermittelt');
|
||||
|
||||
ns.Geo.start(
|
||||
evaluatePosition,
|
||||
error => $('#tracking-status').text(`GPS-Fehler: ${error.message}`)
|
||||
@@ -557,24 +632,35 @@
|
||||
ns.Orientation.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!orientationAvailable) {
|
||||
$('#navigation-mode').text('Norden oben · Navigation');
|
||||
}
|
||||
if (!orientationAvailable) $('#navigation-mode').text('Norden oben · Navigation');
|
||||
}
|
||||
|
||||
function routesFromResponse(response) {
|
||||
if (!Array.isArray(response?.routes)) {
|
||||
throw new TypeError('Die Routen-API hat keine gültige Routenliste geliefert.');
|
||||
}
|
||||
return response.routes.filter(route => route?.id != null && route?.name != null);
|
||||
return response.routes
|
||||
.filter(route => route?.id != null && route?.name != null)
|
||||
.map(route => ({ ...route, source: 'server', local: false }));
|
||||
}
|
||||
|
||||
function loadLocalRoutes() {
|
||||
try {
|
||||
state.localRoutes = ns.LocalRoutes?.listAppRoutes?.() || [];
|
||||
state.localRoutesError = null;
|
||||
} catch (error) {
|
||||
state.localRoutes = [];
|
||||
state.localRoutesError = error.message || 'Lokale Strecken konnten nicht gelesen werden.';
|
||||
}
|
||||
}
|
||||
|
||||
function updateRouteProximity(position) {
|
||||
state.routes = state.routes.map(route => ({
|
||||
const withProximity = route => ({
|
||||
...route,
|
||||
proximityM: route.start ? ns.Distance.meters(position, route.start) : null
|
||||
}));
|
||||
});
|
||||
state.routes = state.routes.map(withProximity);
|
||||
state.localRoutes = state.localRoutes.map(withProximity);
|
||||
}
|
||||
|
||||
async function initializeLocation(context) {
|
||||
@@ -597,7 +683,38 @@
|
||||
}
|
||||
|
||||
function restoreRouteLists() {
|
||||
if (state.routes.length) renderRoutes();
|
||||
loadLocalRoutes();
|
||||
if (state.position) updateRouteProximity(state.position);
|
||||
renderRoutes();
|
||||
}
|
||||
|
||||
function setRouteControlOpen(open) {
|
||||
$('#route-control-dropdown').prop('hidden', !open);
|
||||
$('#route-control-button').attr('aria-expanded', String(open));
|
||||
}
|
||||
|
||||
async function selectRoute(source, id) {
|
||||
setRouteControlOpen(false);
|
||||
const route = source === 'local'
|
||||
? ns.LocalRoutes.getAppRoute(id)
|
||||
: { ...(await ns.Api.route(id)), source: 'server', local: false };
|
||||
renderRoute(route);
|
||||
navigate('#route-page');
|
||||
}
|
||||
|
||||
function refreshCurrentLocalRoute(id) {
|
||||
if (!state.route || state.route.source !== 'local' || String(state.route.id) !== String(id)) return;
|
||||
persistRouteProgress();
|
||||
try {
|
||||
state.route = ns.LocalRoutes.getAppRoute(id);
|
||||
renderRouteView(state.route);
|
||||
renderRouteControl();
|
||||
} catch {
|
||||
state.route = null;
|
||||
state.routeState = 'idle';
|
||||
hideNavigation();
|
||||
navigate('#routes-page', { replace: true });
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
@@ -607,13 +724,11 @@
|
||||
$('#route-search').val('').trigger('input').trigger('focus');
|
||||
});
|
||||
|
||||
activate('[data-route-id]', async function () {
|
||||
activate('[data-route-select]', async function () {
|
||||
const button = $(this);
|
||||
button.prop('disabled', true);
|
||||
try {
|
||||
const route = await ns.Api.route(button.data('route-id'));
|
||||
renderRoute(route);
|
||||
navigate('#route-page');
|
||||
await selectRoute(String(button.data('route-source') || 'server'), String(button.data('route-id')));
|
||||
} catch (error) {
|
||||
alert(error.responseJSON?.message || error.statusText || error.message);
|
||||
} finally {
|
||||
@@ -621,7 +736,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
activate('[data-poi-id]', function () {
|
||||
activate('[data-poi-select]', function () {
|
||||
const poi = state.route?.pois.find(item => item.id === Number($(this).data('poi-id')));
|
||||
if (poi) showPoi(poi);
|
||||
});
|
||||
@@ -632,14 +747,33 @@
|
||||
else navigate(target, { replace: true });
|
||||
});
|
||||
|
||||
activate('#route-control-button', function () {
|
||||
setRouteControlOpen($('#route-control-dropdown').prop('hidden'));
|
||||
});
|
||||
|
||||
activate('#route-control-new', function () {
|
||||
setRouteControlOpen(false);
|
||||
ns.EditorUi?.openNewLocalRoute?.();
|
||||
});
|
||||
|
||||
activate('#editor-button', function () {
|
||||
setRouteControlOpen(false);
|
||||
});
|
||||
|
||||
$(document).on('pointerup', event => {
|
||||
if ($('#route-control-dropdown').prop('hidden')) return;
|
||||
if ($(event.target).closest('.route-control').length) return;
|
||||
setRouteControlOpen(false);
|
||||
});
|
||||
|
||||
$(document).on('keydown', event => {
|
||||
if (event.key === 'Escape') setRouteControlOpen(false);
|
||||
});
|
||||
|
||||
activate('#start-route', async function () {
|
||||
await startRoute();
|
||||
});
|
||||
|
||||
activate('#pause-route', function () {
|
||||
pauseRoute();
|
||||
});
|
||||
|
||||
activate('#pause-route', pauseRoute);
|
||||
activate('#stop-route', function () {
|
||||
endRoute({ returnToRoutes: true });
|
||||
});
|
||||
@@ -654,12 +788,19 @@
|
||||
if (event.detail?.enabled) persistRouteProgress();
|
||||
});
|
||||
|
||||
document.addEventListener('wegwichtel:local-routes-changed', event => {
|
||||
loadLocalRoutes();
|
||||
if (state.position) updateRouteProximity(state.position);
|
||||
refreshCurrentLocalRoute(event.detail?.id);
|
||||
renderRoutes();
|
||||
ns.EditorUi?.refresh?.();
|
||||
});
|
||||
|
||||
globalThis.addEventListener('pagehide', persistRouteProgress);
|
||||
globalThis.addEventListener('popstate', event => {
|
||||
const page = event.state?.page || '#routes-page';
|
||||
navigate(page, { updateHistory: false, instant: true });
|
||||
});
|
||||
|
||||
globalThis.addEventListener('pageshow', restoreRouteLists);
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') restoreRouteLists();
|
||||
@@ -669,6 +810,9 @@
|
||||
|
||||
ns.App = {
|
||||
navigate,
|
||||
currentPosition() {
|
||||
return state.position ? { ...state.position } : null;
|
||||
},
|
||||
|
||||
async initialize(context) {
|
||||
bindEvents();
|
||||
@@ -677,12 +821,14 @@
|
||||
|
||||
const response = await ns.Api.routes(null);
|
||||
state.routes = routesFromResponse(response);
|
||||
loadLocalRoutes();
|
||||
context.markStep('routes', 'done');
|
||||
|
||||
renderRoutes();
|
||||
initializeRouteSearch();
|
||||
updateRouteControls();
|
||||
hideNavigation();
|
||||
setRouteControlOpen(false);
|
||||
|
||||
await initializeLocation(context);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user