Multiple improvements
This commit is contained in:
+39
-1
@@ -22,6 +22,19 @@
|
||||
persistTimer: null
|
||||
};
|
||||
|
||||
let poiMediaGeneration = 0;
|
||||
let localPoiMedia = null;
|
||||
|
||||
function releaseLocalPoiMedia() {
|
||||
poiMediaGeneration += 1;
|
||||
if (localPoiMedia) {
|
||||
ns.AudioPlayer.unload();
|
||||
ns.Slideshow.show([]);
|
||||
ns.LocalRoutes.releasePoiMedia(localPoiMedia);
|
||||
localPoiMedia = null;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return $('<div>').text(value ?? '').html();
|
||||
}
|
||||
@@ -88,6 +101,7 @@
|
||||
const target = $(pageSelector);
|
||||
if (!target.length) throw new Error(ns.I18n.t("app.page.missing"));
|
||||
|
||||
if (pageSelector !== '#poi-page') releaseLocalPoiMedia();
|
||||
const previous = state.activePage;
|
||||
$('.app-page').attr('hidden', true);
|
||||
target.removeAttr('hidden');
|
||||
@@ -287,6 +301,7 @@
|
||||
}
|
||||
|
||||
function endRoute({ announce = true, returnToRoutes = false } = {}) {
|
||||
releaseLocalPoiMedia();
|
||||
const wasActive = state.routeState !== 'idle';
|
||||
state.routeState = 'idle';
|
||||
persistRouteProgress();
|
||||
@@ -330,6 +345,7 @@
|
||||
}
|
||||
|
||||
function suspendCurrentRoute() {
|
||||
releaseLocalPoiMedia();
|
||||
if (!state.route) return;
|
||||
if (state.routeState === 'running') state.routeState = 'paused';
|
||||
persistRouteProgress();
|
||||
@@ -383,6 +399,7 @@
|
||||
}
|
||||
|
||||
function showPoi(poi) {
|
||||
releaseLocalPoiMedia();
|
||||
state.activePoiId = Number(poi.id);
|
||||
state.viewed.add(state.activePoiId);
|
||||
persistRouteProgress();
|
||||
@@ -392,6 +409,27 @@
|
||||
ns.Slideshow.show(poi.images);
|
||||
ns.AudioPlayer.load(poi.audioUrl);
|
||||
navigate('#poi-page');
|
||||
if (state.route?.source === 'local') void loadLocalPoiMedia(poi);
|
||||
}
|
||||
|
||||
async function loadLocalPoiMedia(poi) {
|
||||
const route = state.route;
|
||||
const requestGeneration = poiMediaGeneration;
|
||||
try {
|
||||
const loaded = await ns.LocalRoutes.getAppPoi(route.id, poi.id, { requestPermission: true });
|
||||
if (requestGeneration !== poiMediaGeneration || state.route !== route) {
|
||||
ns.LocalRoutes.releasePoiMedia(loaded);
|
||||
return;
|
||||
}
|
||||
if (!loaded) return;
|
||||
localPoiMedia = loaded;
|
||||
ns.Slideshow.show(loaded.images);
|
||||
ns.AudioPlayer.load(loaded.audioUrl);
|
||||
} catch (error) {
|
||||
if (requestGeneration === poiMediaGeneration) {
|
||||
$('#tracking-status').prop('hidden', false).text(ns.UserMessages?.error(error) || ns.I18n.t('app.error.open-draft'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function nearestRoutePointIndex(position) {
|
||||
@@ -680,7 +718,7 @@
|
||||
async function selectRoute(source, id) {
|
||||
setRouteControlOpen(false);
|
||||
const route = source === 'local'
|
||||
? await ns.LocalRoutes.getAppRoute(id, { requestPermission: true })
|
||||
? await ns.LocalRoutes.getAppRoute(id)
|
||||
: { ...(await ns.Api.route(id)), source: 'server', local: false };
|
||||
renderRoute(route);
|
||||
navigate('#route-page');
|
||||
|
||||
+73
-24
@@ -202,7 +202,17 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
return ns.I18n.t("editor.view-route");
|
||||
}
|
||||
|
||||
let poiMediaGeneration = 0;
|
||||
let previewPoi = null;
|
||||
|
||||
function releasePoiPreview() {
|
||||
poiMediaGeneration += 1;
|
||||
ns.LocalRoutes?.releasePoiMedia?.(previewPoi);
|
||||
previewPoi = null;
|
||||
}
|
||||
|
||||
function showListView() {
|
||||
releasePoiPreview();
|
||||
ns.RouteRecorder?.endEditing();
|
||||
setDialogTitle('Streckenverwaltung');
|
||||
setHidden(byId(ids.routeListView), false);
|
||||
@@ -211,6 +221,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function showRouteView() {
|
||||
releasePoiPreview();
|
||||
setDialogTitle(routeDialogTitle());
|
||||
setHidden(byId(ids.routeListView), true);
|
||||
setHidden(byId(ids.routeDetailView), false);
|
||||
@@ -411,8 +422,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
list.textContent = '';
|
||||
const serverRoutes = state.routes.filter(route => routeSource(route) === 'server');
|
||||
const localRoutes = state.routes.filter(route => routeSource(route) === 'local');
|
||||
renderRouteGroup(list, ns.I18n.t("editor.public-routes"), serverRoutes, ns.I18n.t("app.no-public-route"));
|
||||
renderRouteGroup(list, ns.I18n.t("editor.drafts"), localRoutes, ns.I18n.t("editor.no-draft"));
|
||||
renderRouteGroup(list, ns.I18n.t("editor.public-routes"), serverRoutes, ns.I18n.t("app.no-public-route"));
|
||||
}
|
||||
|
||||
function renderPoiList() {
|
||||
@@ -438,19 +449,30 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}));
|
||||
}
|
||||
|
||||
let routeListGeneration = 0;
|
||||
|
||||
async function loadRoutes() {
|
||||
setStatus(ns.I18n.t("editor.loading-routes"));
|
||||
const payload = await readJson('/routes?activatedOnly=true');
|
||||
const serverRoutes = routeArray(payload).map(route => ({ ...route, _source: 'server' }));
|
||||
let localRoutes = [];
|
||||
try {
|
||||
localRoutes = localRouteSummaries();
|
||||
} catch (error) {
|
||||
setStatus(error.message, true);
|
||||
}
|
||||
state.routes = [...serverRoutes, ...localRoutes];
|
||||
const requestGeneration = ++routeListGeneration;
|
||||
state.routes = [...localRouteSummaries(), ...state.routes.filter(route => routeSource(route) === 'server')];
|
||||
renderRouteList();
|
||||
if (!byId(ids.status)?.dataset.kind || byId(ids.status).dataset.kind !== 'error') setStatus('');
|
||||
setStatus('');
|
||||
// Local drafts are usable even while this request is waiting for a connection.
|
||||
void refreshPublicRoutes(requestGeneration);
|
||||
}
|
||||
|
||||
async function refreshPublicRoutes(requestGeneration) {
|
||||
const listIsVisible = () => byId(ids.dialog)?.open && !byId(ids.routeListView)?.hidden;
|
||||
try {
|
||||
const payload = await readJson('/routes?activatedOnly=true');
|
||||
if (requestGeneration !== routeListGeneration) return;
|
||||
const serverRoutes = routeArray(payload).map(route => ({ ...route, _source: 'server' }));
|
||||
state.routes = [...localRouteSummaries(), ...serverRoutes];
|
||||
renderRouteList();
|
||||
} catch {
|
||||
if (requestGeneration === routeListGeneration && listIsVisible()) {
|
||||
setStatus(ns.I18n.t('editor.public-unavailable'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openEditor() {
|
||||
@@ -471,6 +493,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
releasePoiPreview();
|
||||
ns.RouteRecorder?.endEditing();
|
||||
const dialog = byId(ids.dialog);
|
||||
if (dialog?.open && typeof dialog.close === 'function') dialog.close();
|
||||
@@ -499,6 +522,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function fillRouteForm(route) {
|
||||
setHidden(byId('editor-path-positions'), state.currentSource !== 'local');
|
||||
setHidden(byId('editor-recording-options'), state.currentSource !== 'local');
|
||||
if (state.currentSource === 'local') ns.RouteRecorder?.renderPoints(route.gpx || ns.LocalRoutes.emptyGpx(route.name));
|
||||
byId(ids.routeId).value = routeId(route);
|
||||
byId(ids.routeName).value = routeName(route) === ns.I18n.t("editor.untitled-route") ? '' : routeName(route);
|
||||
@@ -764,14 +788,14 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
async function loadRoute(route) {
|
||||
const id = routeId(route);
|
||||
if (!id) return;
|
||||
if (ns.RouteRecorder?.routeId && (routeSource(route) !== 'local' || ns.RouteRecorder.routeId !== id)) {
|
||||
ns.RouteRecorder.endEditing();
|
||||
if (state.currentRoute && (routeSource(route) !== state.currentSource || routeId(state.currentRoute) !== id)) {
|
||||
ns.RouteRecorder?.endEditing();
|
||||
}
|
||||
setStatus(ns.I18n.t("editor.route.loading"));
|
||||
state.currentSource = routeSource(route);
|
||||
|
||||
if (state.currentSource === 'local') {
|
||||
const routePackage = await ns.LocalRoutes.getWithMedia(id, { requestPermission: true });
|
||||
const routePackage = ns.LocalRoutes.get(id);
|
||||
if (!routePackage) throw new Error(ns.I18n.t("editor.error.draft-missing"));
|
||||
state.currentRoute = {
|
||||
...routePackage.route,
|
||||
@@ -1048,6 +1072,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
|
||||
function newPoi() {
|
||||
releasePoiPreview();
|
||||
const position = currentPosition();
|
||||
const lat = position?.lat ?? null;
|
||||
const lon = position?.lon ?? null;
|
||||
@@ -1065,11 +1090,26 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
showPoiView();
|
||||
}
|
||||
|
||||
function editPoi(poi) {
|
||||
async function editPoi(poi) {
|
||||
if (!poi) return;
|
||||
releasePoiPreview();
|
||||
state.currentPoi = poi;
|
||||
fillPoiForm(poi);
|
||||
byId(ids.poiFormTitle).textContent = ns.I18n.t("editor.edit-station");
|
||||
showPoiView();
|
||||
if (state.currentSource !== 'local') return;
|
||||
const requestGeneration = poiMediaGeneration;
|
||||
const hydrated = await ns.LocalRoutes.getPoiWithMedia(routeId(state.currentRoute), poi.id, { requestPermission: true });
|
||||
if (requestGeneration !== poiMediaGeneration || state.currentPoi !== poi) {
|
||||
ns.LocalRoutes.releasePoiMedia(hydrated);
|
||||
return;
|
||||
}
|
||||
if (!hydrated) return;
|
||||
previewPoi = hydrated;
|
||||
state.currentPoi = hydrated;
|
||||
// Preserve any text edits and newly selected files while existing previews load.
|
||||
renderPoiMediaPreview();
|
||||
updatePoiMediaStatus();
|
||||
}
|
||||
|
||||
async function mediaFromLocalForm(existingPoi, poiIdentifier) {
|
||||
@@ -1122,13 +1162,16 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
route: state.currentRoute,
|
||||
pois: state.pois
|
||||
});
|
||||
const hydrated = await ns.LocalRoutes.getWithMedia(id) || saved;
|
||||
state.currentRoute = { ...hydrated.route, id: hydrated.id, gpx: hydrated.gpx, _source: 'local' };
|
||||
state.pois = hydrated.pois;
|
||||
const previousPoi = state.currentPoi;
|
||||
state.currentRoute = { ...saved.route, id: saved.id, gpx: saved.gpx, _source: 'local' };
|
||||
state.pois = saved.pois;
|
||||
state.currentPoi = currentPoiId
|
||||
? state.pois.find(poi => poiId(poi) === currentPoiId) || null
|
||||
: null;
|
||||
return hydrated;
|
||||
if (state.currentPoi && previousPoi) {
|
||||
state.currentPoi = { ...state.currentPoi, audio: previousPoi.audio, images: previousPoi.images };
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
async function saveLocalPoi(values) {
|
||||
@@ -1241,7 +1284,10 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
updateCurrentPoi(updated);
|
||||
await persistLocalCurrentRoute();
|
||||
if (removedMedia) await ns.LocalRoutes.removeMedia(removedMedia);
|
||||
if (removedMedia) {
|
||||
ns.LocalRoutes.releasePoiMedia({ audio: removedMedia });
|
||||
await ns.LocalRoutes.removeMedia(removedMedia);
|
||||
}
|
||||
state.selectedMediaKey = null;
|
||||
renderPoiMediaPreview();
|
||||
updatePoiMediaStatus();
|
||||
@@ -1416,7 +1462,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
async function poiListAction(event) {
|
||||
const action = event.target.closest('[data-editor-action]')?.dataset.editorAction;
|
||||
if (action === 'edit-poi') {
|
||||
editPoi(poiForItem(event.target));
|
||||
await editPoi(poiForItem(event.target));
|
||||
return;
|
||||
}
|
||||
if (action === 'delete-poi') {
|
||||
@@ -1442,12 +1488,15 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
|
||||
function bind() {
|
||||
for (const eventName of ['cancel', 'close']) {
|
||||
byId(ids.dialog)?.addEventListener(eventName, () => ns.RouteRecorder?.endEditing());
|
||||
byId(ids.dialog)?.addEventListener(eventName, () => {
|
||||
releasePoiPreview();
|
||||
ns.RouteRecorder?.endEditing();
|
||||
});
|
||||
}
|
||||
activate(byId(ids.button), () => guarded(openEditor));
|
||||
activate(byId(ids.close), closeEditor);
|
||||
activate(byId(ids.newRoute), newRoute);
|
||||
activate(byId(ids.routeBack), () => guarded(loadRoutes).then(showListView));
|
||||
activate(byId(ids.routeBack), () => { showListView(); void guarded(loadRoutes); });
|
||||
activate(byId(ids.routeUpload), () => guarded(uploadLocalRoute));
|
||||
activate(byId(ids.rightsOpen), () => ns.LegalUi?.open('terms'));
|
||||
activate(byId(ids.addPoi), newPoi);
|
||||
|
||||
@@ -12,6 +12,7 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const MEDIA_STORE_NAME = 'media';
|
||||
let mediaDatabasePromise = null;
|
||||
let poiCounter = 0;
|
||||
const objectUrls = new Set();
|
||||
|
||||
function storage() {
|
||||
const target = globalThis.localStorage;
|
||||
@@ -195,7 +196,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
const blob = await mediaBlob(media, options);
|
||||
if (!blob) return { ...media, unavailable: true };
|
||||
if (typeof globalThis.URL?.createObjectURL === 'function') {
|
||||
return { ...media, url: globalThis.URL.createObjectURL(blob), unavailable: false };
|
||||
const url = globalThis.URL.createObjectURL(blob);
|
||||
objectUrls.add(url);
|
||||
return { ...media, url, unavailable: false };
|
||||
}
|
||||
const fallback = await fileToDataUrl(blob);
|
||||
return { ...media, dataUrl: fallback.dataUrl, unavailable: false };
|
||||
@@ -204,16 +207,39 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
}
|
||||
}
|
||||
|
||||
async function withMedia(routePackage, options = {}) {
|
||||
if (!routePackage) return null;
|
||||
const pois = await Promise.all((routePackage.pois || []).map(async poi => ({
|
||||
async function poiWithMedia(poi, options = {}) {
|
||||
if (!poi) return null;
|
||||
return {
|
||||
...poi,
|
||||
audio: await mediaForUse(poi.audio, options),
|
||||
images: await Promise.all((poi.images || []).map(media => mediaForUse(media, options)))
|
||||
})));
|
||||
};
|
||||
}
|
||||
|
||||
function releasePoiMedia(poi) {
|
||||
const urls = [poi?.audio?.url, poi?.audioUrl, ...(poi?.images || []).map(image => image.url)];
|
||||
for (const url of urls) {
|
||||
if (!objectUrls.delete(url)) continue;
|
||||
globalThis.URL?.revokeObjectURL?.(url);
|
||||
}
|
||||
}
|
||||
|
||||
async function withMedia(routePackage, options = {}) {
|
||||
if (!routePackage) return null;
|
||||
const pois = await Promise.all((routePackage.pois || []).map(poi => poiWithMedia(poi, options)));
|
||||
return { ...routePackage, pois };
|
||||
}
|
||||
|
||||
async function getPoiWithMedia(id, poiId, options = {}) {
|
||||
const poi = readPackage(id)?.pois.find(item => String(item.id) === String(poiId));
|
||||
return poiWithMedia(poi, options);
|
||||
}
|
||||
|
||||
async function getAppPoi(id, poiId, options = {}) {
|
||||
const poi = await getPoiWithMedia(id, poiId, options);
|
||||
return poi ? appPoi(poi) : null;
|
||||
}
|
||||
|
||||
function mediaForStorage(media) {
|
||||
if (!media) return null;
|
||||
const stored = { ...media };
|
||||
@@ -709,8 +735,8 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
return listPackages().map(toAppRoute);
|
||||
}
|
||||
|
||||
async function getAppRoute(id, options = {}) {
|
||||
const routePackage = await withMedia(readPackage(id), options);
|
||||
async function getAppRoute(id) {
|
||||
const routePackage = readPackage(id);
|
||||
if (!routePackage) throw new Error(ns.I18n.t("local.error.route-missing"));
|
||||
return toAppRoute(routePackage);
|
||||
}
|
||||
@@ -762,6 +788,9 @@ globalThis.Wegwichtel = globalThis.Wegwichtel || {};
|
||||
get: readPackage,
|
||||
getAppRoute,
|
||||
getWithMedia,
|
||||
getPoiWithMedia,
|
||||
getAppPoi,
|
||||
releasePoiMedia,
|
||||
gpxForUpload,
|
||||
list: listPackages,
|
||||
listAppRoutes,
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
globalThis.Wegwichtel ??= {};
|
||||
(function (ns) {
|
||||
'use strict';
|
||||
const INTERVAL_MS = 30000;
|
||||
const INTERVAL_MS = 120000;
|
||||
let id = null;
|
||||
let editingId = null;
|
||||
let status = 'idle';
|
||||
let timer = null;
|
||||
let generation = 0;
|
||||
let pending = false;
|
||||
let pendingGeneration = null;
|
||||
let enabled = false;
|
||||
let manualGeneration = 0;
|
||||
let message = '';
|
||||
let manualFields = [];
|
||||
let lastPosition = null;
|
||||
|
||||
function render() {
|
||||
const toggle = document.getElementById('route-recording-toggle');
|
||||
if (toggle) { toggle.checked = enabled; toggle.setAttribute('aria-checked', String(enabled)); }
|
||||
const control = document.getElementById('route-recording-control');
|
||||
if (!control) return;
|
||||
control.hidden = status === 'idle';
|
||||
@@ -21,9 +25,9 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
async function sample() {
|
||||
if (pending || status !== 'recording') return;
|
||||
pending = true;
|
||||
if (pendingGeneration === generation || status !== 'recording') return;
|
||||
const currentGeneration = generation;
|
||||
pendingGeneration = currentGeneration;
|
||||
try {
|
||||
let position;
|
||||
try { position = await ns.Geo.current({ maximumAge: 0 }); }
|
||||
@@ -40,7 +44,7 @@ globalThis.Wegwichtel ??= {};
|
||||
ns.StorageUsage?.reportError(error);
|
||||
message = ns.I18n.t('recorder.save-error');
|
||||
} finally {
|
||||
pending = false;
|
||||
if (pendingGeneration === currentGeneration) pendingGeneration = null;
|
||||
render();
|
||||
}
|
||||
}
|
||||
@@ -69,6 +73,7 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
function stop() {
|
||||
enabled = false;
|
||||
clearTimer();
|
||||
id = null;
|
||||
lastPosition = null;
|
||||
@@ -78,6 +83,7 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
function start(routeId) {
|
||||
enabled = true;
|
||||
lastPosition = null;
|
||||
id = String(routeId);
|
||||
editingId = id;
|
||||
@@ -85,16 +91,46 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
|
||||
function beginEditing(routeId) {
|
||||
if (editingId === String(routeId)) return;
|
||||
stop();
|
||||
start(routeId);
|
||||
const nextId = String(routeId);
|
||||
if (editingId === nextId) return;
|
||||
if (editingId !== null) endEditing();
|
||||
editingId = nextId;
|
||||
if (enabled) { id = editingId; resume(); }
|
||||
render();
|
||||
}
|
||||
|
||||
function setEnabled(value) {
|
||||
if (!value) { stop(); return; }
|
||||
enabled = true;
|
||||
if (editingId) { id = editingId; resume(); }
|
||||
render();
|
||||
}
|
||||
|
||||
function endEditing() {
|
||||
editingId = null;
|
||||
manualGeneration += 1;
|
||||
stop();
|
||||
}
|
||||
|
||||
async function fillCurrentLocation(field, button, notice) {
|
||||
if (button.disabled) return;
|
||||
const currentGeneration = manualGeneration;
|
||||
button.disabled = true;
|
||||
notice.textContent = ns.I18n.t('recorder.locating');
|
||||
try {
|
||||
const position = await ns.Geo.current({ maximumAge: 0 });
|
||||
if (currentGeneration !== manualGeneration || !manualFields.includes(field)) return;
|
||||
if (!Number.isFinite(position.lat) || !Number.isFinite(position.lon) || Math.abs(position.lat) > 90 || Math.abs(position.lon) > 180) throw new Error('Invalid location');
|
||||
field.lat.value = String(position.lat);
|
||||
field.lon.value = String(position.lon);
|
||||
notice.textContent = ns.I18n.t('recorder.location-set');
|
||||
} catch {
|
||||
if (currentGeneration === manualGeneration && manualFields.includes(field)) notice.textContent = ns.I18n.t('recorder.location-error');
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addPositionField(position = {}, index = null) {
|
||||
const container = document.getElementById('editor-recorded-positions');
|
||||
if (!container) return;
|
||||
@@ -114,13 +150,30 @@ globalThis.Wegwichtel ??= {};
|
||||
row.appendChild(label); row.appendChild(input);
|
||||
fields[key] = input;
|
||||
}
|
||||
manualFields.push({ index, original: position, ...fields });
|
||||
const field = { index, original: position, ...fields };
|
||||
manualFields.push(field);
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'current-location-button';
|
||||
button.setAttribute('aria-label', ns.I18n.t('recorder.use-location'));
|
||||
button.title = ns.I18n.t('recorder.use-location');
|
||||
const icon = document.createElement('img');
|
||||
icon.src = '/images/icons/current-location.svg';
|
||||
icon.alt = '';
|
||||
button.appendChild(icon);
|
||||
const notice = document.createElement('p');
|
||||
notice.className = 'editor-muted';
|
||||
notice.setAttribute('aria-live', 'polite');
|
||||
button.addEventListener('click', () => { void fillCurrentLocation(field, button, notice); });
|
||||
row.appendChild(button);
|
||||
row.appendChild(notice);
|
||||
container.appendChild(row);
|
||||
}
|
||||
|
||||
function renderPoints(gpx) {
|
||||
const container = document.getElementById('editor-recorded-positions');
|
||||
if (!container) return;
|
||||
manualGeneration += 1;
|
||||
container.replaceChildren();
|
||||
manualFields = [];
|
||||
ns.LocalRoutes.recordedPositions(gpx).forEach(addPositionField);
|
||||
@@ -146,6 +199,10 @@ globalThis.Wegwichtel ??= {};
|
||||
|
||||
function bind() {
|
||||
globalThis.addEventListener?.('pagehide', endEditing);
|
||||
document.getElementById('route-recording-toggle')?.addEventListener('change', event => setEnabled(event.target.checked));
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible' && status === 'recording') void sample();
|
||||
});
|
||||
const activate = (elementId, action) => {
|
||||
const element = document.getElementById(elementId);
|
||||
element?.addEventListener('pointerup', event => {
|
||||
|
||||
@@ -129,6 +129,7 @@ globalThis.Wegwichtel ??= {};
|
||||
"drafts": "Meine Entwürfe",
|
||||
"no-draft": "Noch kein eigener Entwurf vorhanden.",
|
||||
"no-stations": "Noch keine Stationen angelegt.",
|
||||
"public-unavailable": "Die veröffentlichten Strecken sind gerade nicht erreichbar. Deine Entwürfe auf diesem Gerät kannst du weiter bearbeiten.",
|
||||
"loading-routes": "Strecken werden geladen …",
|
||||
"submitted": "Erneut zur Prüfung senden",
|
||||
"rights-hint": "Wenn du Bilder oder Tonaufnahmen sendest, erlaubst du Wegwichtel, sie nach der Nutzungsvereinbarung zu verwenden. Sende nur Aufnahmen, für die du diese Erlaubnis geben darfst. Kinder brauchen die Zustimmung ihrer Sorgeberechtigten.",
|
||||
@@ -452,6 +453,13 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
},
|
||||
"recorder": {
|
||||
"toggle": "Meinen Weg aufnehmen",
|
||||
"toggle-help": "Alle zwei Minuten wird dein Standort auf diesem Gerät gespeichert. Die Aufnahme endet, wenn du die Bearbeitung verlässt.",
|
||||
"use-location": "Meinen Standort einsetzen",
|
||||
"locating": "Dein Standort wird gesucht …",
|
||||
"location-set": "Dein Standort ist eingetragen. Speichere jetzt deine Änderungen.",
|
||||
"location-error": "Dein Standort konnte nicht gefunden werden. Erlaube den Zugriff auf deinen Standort und versuche es noch einmal.",
|
||||
|
||||
"recording": "Dein Weg wird aufgenommen.",
|
||||
"paused": "Die Aufnahme macht eine Pause.",
|
||||
"pause": "Aufnahme pausieren",
|
||||
|
||||
@@ -129,6 +129,7 @@ globalThis.Wegwichtel ??= {};
|
||||
"drafts": "My drafts",
|
||||
"no-draft": "There are no drafts yet.",
|
||||
"no-stations": "There are no stops yet.",
|
||||
"public-unavailable": "Published trails are currently unavailable. You can still edit your drafts on this device.",
|
||||
"loading-routes": "Loading trails …",
|
||||
"submitted": "Send changes for review",
|
||||
"rights-hint": "By sending pictures or recordings, you allow Wegwichtel to use them under its terms of use. Only send recordings you are allowed to share this way. Children need their parent or guardian’s permission.",
|
||||
@@ -452,6 +453,13 @@ globalThis.Wegwichtel ??= {};
|
||||
}
|
||||
},
|
||||
"recorder": {
|
||||
"toggle": "Record my path",
|
||||
"toggle-help": "Your location is saved on this device every two minutes. Recording stops when you leave the editor.",
|
||||
"use-location": "Use my current location",
|
||||
"locating": "Finding your location …",
|
||||
"location-set": "Your location has been entered. Save your changes now.",
|
||||
"location-error": "Your location could not be found. Allow access to your location and try again.",
|
||||
|
||||
"recording": "Your trail is being recorded.",
|
||||
"paused": "Recording is paused.",
|
||||
"pause": "Pause recording",
|
||||
|
||||
Reference in New Issue
Block a user