globalThis.Wegwichtel ??= {}; (function (ns) { 'use strict'; const INTERVAL_MS = 30000; let id = null; let editingId = null; let status = 'idle'; let timer = null; let generation = 0; let pending = false; let message = ''; let manualFields = []; let lastPosition = null; function render() { const control = document.getElementById('route-recording-control'); if (!control) return; control.hidden = status === 'idle'; document.getElementById('route-recording-status').textContent = status === 'idle' ? '' : message || ns.I18n.t(`recorder.${status}`); document.getElementById('route-recording-pause').textContent = ns.I18n.t(status === 'paused' ? 'recorder.resume' : 'recorder.pause'); } async function sample() { if (pending || status !== 'recording') return; pending = true; const currentGeneration = generation; try { let position; try { position = await ns.Geo.current({ maximumAge: 0 }); } catch { position = { timestamp: Date.now() }; } if (currentGeneration !== generation || status !== 'recording') return; lastPosition = Number.isFinite(position.lat) && Number.isFinite(position.lon) ? position : null; const route = ns.LocalRoutes.get(id); if (!route) { stop(); return; } const gpx = ns.LocalRoutes.appendTrackPoint(route.gpx, position, route.route.name); ns.LocalRoutes.save({ ...route, gpx }); message = ns.I18n.t(Number.isFinite(position.lat) && Number.isFinite(position.lon) ? 'recorder.recording' : 'recorder.no-location'); } catch (error) { pause(); ns.StorageUsage?.reportError(error); message = ns.I18n.t('recorder.save-error'); } finally { pending = false; render(); } } function clearTimer() { if (timer !== null) globalThis.clearInterval(timer); timer = null; generation += 1; } function resume() { if (!id) return; clearTimer(); status = 'recording'; message = ''; timer = globalThis.setInterval(sample, INTERVAL_MS); sample(); render(); } function pause() { clearTimer(); status = id ? 'paused' : 'idle'; message = ''; render(); } function stop() { clearTimer(); id = null; lastPosition = null; status = 'idle'; message = ''; render(); } function start(routeId) { lastPosition = null; id = String(routeId); editingId = id; resume(); } function beginEditing(routeId) { if (editingId === String(routeId)) return; stop(); start(routeId); } function endEditing() { editingId = null; stop(); } function addPositionField(position = {}, index = null) { const container = document.getElementById('editor-recorded-positions'); if (!container) return; const row = document.createElement('fieldset'); const legend = document.createElement('legend'); legend.textContent = ns.I18n.t('recorder.position-number', { number: manualFields.length + 1 }); row.appendChild(legend); const fields = {}; for (const [key, textKey, minimum, maximum] of [['lat', 'latitude', -90, 90], ['lon', 'longitude', -180, 180]]) { const label = document.createElement('label'); const input = document.createElement('input'); input.id = `recorded-position-${manualFields.length}-${key}`; label.setAttribute('for', input.id); label.textContent = ns.I18n.t(`recorder.${textKey}`); input.type = 'number'; input.step = 'any'; input.min = minimum; input.max = maximum; input.value = position[key] == null ? '' : String(position[key]); row.appendChild(label); row.appendChild(input); fields[key] = input; } manualFields.push({ index, original: position, ...fields }); container.appendChild(row); } function renderPoints(gpx) { const container = document.getElementById('editor-recorded-positions'); if (!container) return; container.replaceChildren(); manualFields = []; ns.LocalRoutes.recordedPositions(gpx).forEach(addPositionField); } function editGpx(gpx, routeName) { let changed = false; const positions = ns.LocalRoutes.recordedPositions(gpx); for (const field of manualFields) { const values = ['lat', 'lon'].map(key => field[key].value.trim() === '' ? null : Number(field[key].value)); const [lat, lon] = values; if ((lat === null) !== (lon === null) || (lat !== null && (!Number.isFinite(lat) || !Number.isFinite(lon) || Math.abs(lat) > 90 || Math.abs(lon) > 180))) { throw new Error(ns.I18n.t('editor.error.station-location')); } if (field.index !== null && lat === field.original.lat && lon === field.original.lon) continue; const point = { ...field.original, lat, lon }; if (field.index === null) positions.push(point); else positions[field.index] = point; changed = true; } return changed ? ns.LocalRoutes.replaceRecordedPositions(gpx, positions, routeName) : gpx; } function bind() { globalThis.addEventListener?.('pagehide', endEditing); const activate = (elementId, action) => { const element = document.getElementById(elementId); element?.addEventListener('pointerup', event => { if (event.isPrimary === false || (event.pointerType === 'mouse' && event.button !== 0)) return; event.preventDefault(); action(); }); element?.addEventListener('keydown', event => { if (event.key !== 'Enter' && event.key !== ' ') return; event.preventDefault(); action(); }); }; activate('route-recording-pause', () => status === 'recording' ? pause() : resume()); activate('route-recording-stop', stop); activate('editor-add-position', () => addPositionField({ timestamp: new Date().toISOString() })); document.addEventListener('wegwichtel:local-routes-changed', () => { if (id && !ns.LocalRoutes.get(id)) stop(); }); } ns.RouteRecorder = Object.freeze({ start, pause, resume, stop, beginEditing, endEditing, renderPoints, editGpx, get status() { return status; }, get routeId() { return id; }, get position() { return lastPosition; } }); if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', bind, { once: true }); else bind(); }(globalThis.Wegwichtel));