Sprint 11: Freunde & Chat + Phosphor-Icon-Vollmigration
- Freundschaften (pending/accepted), Nutzersuche, Anfragen per Push - Direktnachrichten mit Polling, iMessage-Stil, Deep-Links aus Push - Alle Seiten (map, places, diary, health, dog-profile, sitting, knigge, forum, wiki, walks) vollständig auf Phosphor-Icons migriert - Wikidata-Rassen-Scraper (~833 neue Rassen, lokal gespiegelte Fotos) - TheDogAPI lokal gespiegelt (169 Rassen + Fotos) - Quiz-Result-Cards horizontal (korrekte Bildproportionen) - SW by-v89
This commit is contained in:
parent
96bd57f0ad
commit
097295c628
44 changed files with 9980 additions and 300 deletions
|
|
@ -28,6 +28,8 @@ window.Page_map = (() => {
|
|||
let _recStartTime = null;
|
||||
let _recTimerInt = null;
|
||||
let _recPolyline = null;
|
||||
let _pocketOverlay = null;
|
||||
let _pocketHideTimer = null;
|
||||
let _recMarker = null;
|
||||
let _recWatchId = null;
|
||||
|
||||
|
|
@ -71,22 +73,22 @@ window.Page_map = (() => {
|
|||
|
||||
// z: zIndexOffset — höher = weiter oben bei Überlappung
|
||||
const TYPEN = {
|
||||
restaurant: { icon: '🍽️', label: 'Restaurant', color: '#F97316', z: 10 },
|
||||
freilauf: { icon: '🐕', label: 'Freilauf', color: '#22C55E', z: 20 },
|
||||
shop: { icon: '🛒', label: 'Shop', color: '#3B82F6', z: 15 },
|
||||
kotbeutel: { icon: '🧻', label: 'Kotbeutel', color: '#6B7280', z: 5 },
|
||||
tierarzt: { icon: '🩺', label: 'Tierarzt', color: '#EF4444', z: 40 },
|
||||
hundeschule: { icon: '🎓', label: 'Hundeschule', color: '#8B5CF6', z: 30 },
|
||||
poison: { icon: '⚠️', label: 'Giftköder', color: '#DC2626', z: 100 },
|
||||
muell: { icon: '🗑️', label: 'Mülleimer', color: '#78716C', z: -20 },
|
||||
dog_park: { icon: '🌿', label: 'Hundewiese', color: '#15803D', z: 5 },
|
||||
wasser: { icon: '💧', label: 'Wasserstelle', color: '#0EA5E9', z: 35 },
|
||||
bank: { icon: '🪑', label: 'Bank', color: '#92400E', z: -30 },
|
||||
giftkoeder: { icon: '☠️', label: 'Giftköder', color: '#DC2626', z: 80 },
|
||||
gefahr: { icon: '⚠️', label: 'Gefahr', color: '#F59E0B', z: 60 },
|
||||
parkplatz: { icon: '🅿️', label: 'Parkplatz', color: '#2563EB', z: 5 },
|
||||
treffpunkt: { icon: '🤝', label: 'Treffpunkt', color: '#7C3AED', z: 25 },
|
||||
community: { icon: '📌', label: 'Sonstiges', color: '#F59E0B', z: 30 },
|
||||
restaurant: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#fork-knife"></use></svg>', label: 'Restaurant', color: '#F97316', z: 10 },
|
||||
freilauf: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>', label: 'Freilauf', color: '#22C55E', z: 20 },
|
||||
shop: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#shopping-cart"></use></svg>', label: 'Shop', color: '#3B82F6', z: 15 },
|
||||
kotbeutel: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>', label: 'Kotbeutel', color: '#6B7280', z: 5 },
|
||||
tierarzt: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg>', label: 'Tierarzt', color: '#EF4444', z: 40 },
|
||||
hundeschule: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#graduation-cap"></use></svg>', label: 'Hundeschule', color: '#8B5CF6', z: 30 },
|
||||
poison: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg>', label: 'Giftköder', color: '#DC2626', z: 100 },
|
||||
muell: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>', label: 'Mülleimer', color: '#78716C', z: -20 },
|
||||
dog_park: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#leaf"></use></svg>', label: 'Hundewiese', color: '#15803D', z: 5 },
|
||||
wasser: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#drop"></use></svg>', label: 'Wasserstelle', color: '#0EA5E9', z: 35 },
|
||||
bank: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#chair"></use></svg>', label: 'Bank', color: '#92400E', z: -30 },
|
||||
giftkoeder: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#skull"></use></svg>', label: 'Giftköder', color: '#DC2626', z: 80 },
|
||||
gefahr: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg>', label: 'Gefahr', color: '#F59E0B', z: 60 },
|
||||
parkplatz: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#car"></use></svg>', label: 'Parkplatz', color: '#2563EB', z: 5 },
|
||||
treffpunkt: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#handshake"></use></svg>', label: 'Treffpunkt', color: '#7C3AED', z: 25 },
|
||||
community: { icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>', label: 'Sonstiges', color: '#F59E0B', z: 30 },
|
||||
};
|
||||
|
||||
// Frontend-Layer → Backend-Typ Mapping
|
||||
|
|
@ -150,7 +152,7 @@ window.Page_map = (() => {
|
|||
|
||||
<div class="map-legend" id="map-legend">
|
||||
<button class="map-legend-btn map-legend-all" id="map-legend-all" title="Alle ein-/ausblenden">
|
||||
☰
|
||||
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#list"></use></svg>
|
||||
</button>
|
||||
${Object.entries(TYPEN).filter(([k]) => k !== 'giftkoeder').map(([k, t]) => `
|
||||
<button class="map-legend-btn${_visible[k] !== false ? ' active' : ''}" data-layer="${k}" style="--layer-color:${t.color}">
|
||||
|
|
@ -163,22 +165,22 @@ window.Page_map = (() => {
|
|||
|
||||
<!-- Fadenkreuz-Overlay (nur im Placement-Modus sichtbar) -->
|
||||
<div class="map-crosshair" id="map-crosshair">
|
||||
<div class="map-crosshair-pin">📍</div>
|
||||
<div class="map-crosshair-pin"><svg class="ph-icon" aria-hidden="true" style="width:28px;height:28px"><use href="/icons/phosphor.svg#map-pin"></use></svg></div>
|
||||
<div class="map-crosshair-shadow"></div>
|
||||
</div>
|
||||
<div class="map-place-bar" id="map-place-bar">
|
||||
<span class="map-place-hint">Karte verschieben · Pin landet genau hier</span>
|
||||
<div class="map-place-btns">
|
||||
<button class="btn btn-secondary" id="map-place-cancel">Abbrechen</button>
|
||||
<button class="btn btn-primary" id="map-place-confirm">📌 Hier platzieren</button>
|
||||
<button class="btn btn-primary" id="map-place-confirm"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg> Hier platzieren</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="map-fabs">
|
||||
<button class="map-fab map-fab--rec" id="map-rec-btn" title="Route aufzeichnen">🔴</button>
|
||||
<button class="map-fab map-fab--offline" id="map-offline-btn" title="Karte offline speichern">💾</button>
|
||||
<button class="map-fab map-fab--pin" id="map-pin-btn" title="Marker setzen">📌</button>
|
||||
<button class="map-fab" id="map-locate-btn" title="Meinen Standort">📍</button>
|
||||
<button class="map-fab map-fab--rec" id="map-rec-btn" title="Route aufzeichnen"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg></button>
|
||||
<button class="map-fab map-fab--offline" id="map-offline-btn" title="Karte offline speichern"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg></button>
|
||||
<button class="map-fab map-fab--pin" id="map-pin-btn" title="Marker setzen"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg></button>
|
||||
<button class="map-fab" id="map-locate-btn" title="Meinen Standort"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg></button>
|
||||
</div>
|
||||
|
||||
<div class="map-statusbar" id="map-statusbar">
|
||||
|
|
@ -203,11 +205,11 @@ window.Page_map = (() => {
|
|||
</div>
|
||||
</div>
|
||||
<div class="map-rec-actions">
|
||||
<button class="btn btn-secondary map-rec-action-btn" id="rec-panel-pause">⏸ Pause</button>
|
||||
<button class="btn btn-danger map-rec-action-btn" id="rec-panel-stop">⏹ Speichern</button>
|
||||
<button class="btn btn-secondary map-rec-action-btn" id="rec-panel-pause">Pause</button>
|
||||
<button class="btn btn-danger map-rec-action-btn" id="rec-panel-stop"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg> Speichern</button>
|
||||
</div>
|
||||
<div class="map-rec-hint" id="map-rec-hint">
|
||||
📵 Bildschirm bleibt aktiv — GPS läuft
|
||||
Bildschirm bleibt aktiv — GPS läuft
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -512,7 +514,7 @@ window.Page_map = (() => {
|
|||
html: `<div class="poison-marker">
|
||||
<div class="poison-ring"></div>
|
||||
<div class="poison-ring"></div>
|
||||
<div class="poison-dot">☠️</div>
|
||||
<div class="poison-dot"><svg class="ph-icon" aria-hidden="true" style="width:20px;height:20px"><use href="/icons/phosphor.svg#skull"></use></svg></div>
|
||||
</div>`,
|
||||
iconSize: [48, 48],
|
||||
iconAnchor: [24, 24],
|
||||
|
|
@ -562,11 +564,11 @@ window.Page_map = (() => {
|
|||
: `<button class="btn btn-secondary btn-sm" id="mp-action">Als ungültig melden</button>`;
|
||||
|
||||
const openHours = poi.opening_hours
|
||||
? `<div style="font-size:11px;color:#555;margin-bottom:4px">🕐 ${poi.opening_hours}</div>` : '';
|
||||
? `<div style="font-size:11px;color:#555;margin-bottom:4px"><svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#clock"></use></svg> ${poi.opening_hours}</div>` : '';
|
||||
const phone = poi.phone
|
||||
? `<div style="font-size:11px;margin-bottom:4px"><a href="tel:${poi.phone}" style="color:var(--c-primary);text-decoration:none">📞 ${poi.phone}</a></div>` : '';
|
||||
? `<div style="font-size:11px;margin-bottom:4px"><a href="tel:${poi.phone}" style="color:var(--c-primary);text-decoration:none">${poi.phone}</a></div>` : '';
|
||||
const website = poi.website
|
||||
? `<div style="font-size:11px;margin-bottom:6px"><a href="${poi.website}" target="_blank" rel="noopener" style="color:var(--c-primary);text-decoration:none">🌐 Website</a></div>` : '';
|
||||
? `<div style="font-size:11px;margin-bottom:6px"><a href="${poi.website}" target="_blank" rel="noopener" style="color:var(--c-primary);text-decoration:none"><svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#arrow-square-out"></use></svg> Website</a></div>` : '';
|
||||
|
||||
marker.bindPopup(`
|
||||
<div style="min-width:170px;max-width:240px">
|
||||
|
|
@ -575,8 +577,8 @@ window.Page_map = (() => {
|
|||
${openHours}${phone}${website}
|
||||
<div style="font-size:11px;color:#999;margin-bottom:10px">
|
||||
${isUser
|
||||
? `📌 Community-Pin${poi.username ? ' · <b>' + poi.username + '</b>' : ''}`
|
||||
: '🗺️ OpenStreetMap'}
|
||||
? `<svg class="ph-icon" aria-hidden="true" style="width:11px;height:11px"><use href="/icons/phosphor.svg#push-pin"></use></svg> Community-Pin${poi.username ? ' · <b>' + poi.username + '</b>' : ''}`
|
||||
: '<svg class="ph-icon" aria-hidden="true" style="width:11px;height:11px"><use href="/icons/phosphor.svg#map-trifold"></use></svg> OpenStreetMap'}
|
||||
</div>
|
||||
${actionBtn}
|
||||
</div>
|
||||
|
|
@ -618,7 +620,7 @@ window.Page_map = (() => {
|
|||
_placingMarker = false;
|
||||
const btn = document.getElementById('map-pin-btn');
|
||||
btn?.classList.remove('active');
|
||||
btn && (btn.textContent = '\uD83D\uDCCC');
|
||||
btn && (btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>');
|
||||
document.getElementById('map-crosshair')?.classList.remove('active', 'dragging');
|
||||
document.getElementById('map-place-bar')?.classList.remove('active');
|
||||
_tempMarker?.remove();
|
||||
|
|
@ -626,14 +628,14 @@ window.Page_map = (() => {
|
|||
}
|
||||
|
||||
const PIN_TYPES = [
|
||||
{ type: 'giftkoeder', icon: '☠️', label: 'Giftköder', color: '#DC2626' }, // ← wichtigster Typ, immer oben
|
||||
{ type: 'waste_basket', icon: '🗑️', label: 'Mülleimer', color: '#78716C' },
|
||||
{ type: 'kotbeutel', icon: '🧻', label: 'Kotbeutel', color: '#6B7280' },
|
||||
{ type: 'drinking_water', icon: '💧', label: 'Wasserstelle', color: '#0EA5E9' },
|
||||
{ type: 'dog_park', icon: '🌿', label: 'Hundewiese', color: '#15803D' },
|
||||
{ type: 'parkplatz', icon: '🅿️', label: 'Parkplatz', color: '#2563EB' },
|
||||
{ type: 'treffpunkt', icon: '🤝', label: 'Treffpunkt', color: '#7C3AED' },
|
||||
{ type: 'sonstiges', icon: '📌', label: 'Sonstiges', color: '#F59E0B' },
|
||||
{ type: 'giftkoeder', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#skull"></use></svg>', label: 'Giftköder', color: '#DC2626' }, // ← wichtigster Typ, immer oben
|
||||
{ type: 'waste_basket', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>', label: 'Mülleimer', color: '#78716C' },
|
||||
{ type: 'kotbeutel', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>', label: 'Kotbeutel', color: '#6B7280' },
|
||||
{ type: 'drinking_water', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#drop"></use></svg>', label: 'Wasserstelle', color: '#0EA5E9' },
|
||||
{ type: 'dog_park', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#leaf"></use></svg>', label: 'Hundewiese', color: '#15803D' },
|
||||
{ type: 'parkplatz', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#car"></use></svg>', label: 'Parkplatz', color: '#2563EB' },
|
||||
{ type: 'treffpunkt', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#handshake"></use></svg>', label: 'Treffpunkt', color: '#7C3AED' },
|
||||
{ type: 'sonstiges', icon: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg>', label: 'Sonstiges', color: '#F59E0B' },
|
||||
];
|
||||
|
||||
function _confirmPlacement(latlng) {
|
||||
|
|
@ -645,7 +647,7 @@ window.Page_map = (() => {
|
|||
let _selectedType = 'giftkoeder';
|
||||
|
||||
UI.modal.open({
|
||||
title: '📌 Marker setzen',
|
||||
title: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#push-pin"></use></svg> Marker setzen',
|
||||
body: `
|
||||
<form id="poi-form" class="flex flex-col gap-3">
|
||||
<div>
|
||||
|
|
@ -930,7 +932,7 @@ window.Page_map = (() => {
|
|||
navigator.serviceWorker.removeEventListener('message', onMessage);
|
||||
if (btn) btn.classList.remove('loading');
|
||||
_setOsmStatus('');
|
||||
UI.toast.success(`\u2705 ${total} Kacheln offline gespeichert!`);
|
||||
UI.toast.success(`${total} Kacheln offline gespeichert!`);
|
||||
} else {
|
||||
_setOsmStatus(`Offline: ${done} / ${total} Kacheln…`);
|
||||
}
|
||||
|
|
@ -940,6 +942,92 @@ window.Page_map = (() => {
|
|||
navigator.serviceWorker.controller.postMessage({ type: 'CACHE_TILES', urls });
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Pocket-Modus Overlay
|
||||
// ----------------------------------------------------------
|
||||
function _showPocketOverlay() {
|
||||
if (_pocketOverlay) return;
|
||||
const el = document.createElement('div');
|
||||
el.id = 'pocket-overlay';
|
||||
el.innerHTML = `
|
||||
<div class="po-status" id="po-status">GPS läuft</div>
|
||||
<div class="po-time" id="po-time">0:00</div>
|
||||
<div class="po-dist" id="po-dist">0.00 km</div>
|
||||
<div class="po-hint">Tippen für Steuerung</div>
|
||||
<div class="po-controls" id="po-controls">
|
||||
<button class="po-btn" id="po-pause">⏸ Pause</button>
|
||||
<button class="po-btn po-btn--stop" id="po-stop">⏹ Stopp</button>
|
||||
</div>
|
||||
`;
|
||||
el.style.cssText = `
|
||||
position:fixed;inset:0;z-index:9998;background:#000;
|
||||
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
||||
color:#fff;font-family:inherit;user-select:none;
|
||||
`;
|
||||
el.querySelector('.po-status').style.cssText =
|
||||
'font-size:0.85rem;color:#888;margin-bottom:1rem;letter-spacing:0.05em;text-transform:uppercase';
|
||||
el.querySelector('.po-time').style.cssText =
|
||||
'font-size:4.5rem;font-weight:700;letter-spacing:-0.02em;line-height:1';
|
||||
el.querySelector('.po-dist').style.cssText =
|
||||
'font-size:1.5rem;color:#aaa;margin-top:0.5rem';
|
||||
el.querySelector('.po-hint').style.cssText =
|
||||
'font-size:0.75rem;color:#444;margin-top:2.5rem';
|
||||
const ctrl = el.querySelector('.po-controls');
|
||||
ctrl.style.cssText =
|
||||
'display:none;gap:1rem;margin-top:2rem;flex-direction:row';
|
||||
el.querySelectorAll('.po-btn').forEach(b => {
|
||||
b.style.cssText =
|
||||
'padding:0.75rem 1.5rem;border:1px solid #555;border-radius:0.75rem;' +
|
||||
'background:#111;color:#fff;font-size:1rem;cursor:pointer';
|
||||
});
|
||||
el.querySelector('.po-btn--stop').style.cssText +=
|
||||
'border-color:#c0392b;color:#e74c3c';
|
||||
|
||||
// Tippen → Controls 4s einblenden, dann ausblenden
|
||||
el.addEventListener('click', e => {
|
||||
if (e.target.closest('#po-controls')) return;
|
||||
ctrl.style.display = 'flex';
|
||||
el.querySelector('.po-hint').style.color = '#666';
|
||||
clearTimeout(_pocketHideTimer);
|
||||
_pocketHideTimer = setTimeout(() => {
|
||||
ctrl.style.display = 'none';
|
||||
el.querySelector('.po-hint').style.color = '#444';
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
el.querySelector('#po-pause').addEventListener('click', () => {
|
||||
_togglePause();
|
||||
el.querySelector('#po-pause').textContent =
|
||||
_recPaused ? '▶ Weiter' : '⏸ Pause';
|
||||
el.querySelector('#po-status').textContent =
|
||||
_recPaused ? 'Pausiert' : 'GPS läuft';
|
||||
});
|
||||
el.querySelector('#po-stop').addEventListener('click', () => {
|
||||
_hidePocketOverlay();
|
||||
_stopRecording();
|
||||
});
|
||||
|
||||
document.body.appendChild(el);
|
||||
_pocketOverlay = el;
|
||||
}
|
||||
|
||||
function _hidePocketOverlay() {
|
||||
clearTimeout(_pocketHideTimer);
|
||||
_pocketOverlay?.remove();
|
||||
_pocketOverlay = null;
|
||||
}
|
||||
|
||||
function _updatePocketOverlay() {
|
||||
if (!_pocketOverlay) return;
|
||||
const elapsed = Math.floor((Date.now() - _recStartTime) / 1000);
|
||||
const mm = String(Math.floor(elapsed / 60)).padStart(1, '0');
|
||||
const ss = String(elapsed % 60).padStart(2, '0');
|
||||
const timeEl = _pocketOverlay.querySelector('#po-time');
|
||||
const distEl = _pocketOverlay.querySelector('#po-dist');
|
||||
if (timeEl) timeEl.textContent = `${mm}:${ss}`;
|
||||
if (distEl) distEl.textContent = `${_recDistKm.toFixed(2)} km`;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// GPS-Aufzeichnung
|
||||
// ----------------------------------------------------------
|
||||
|
|
@ -966,7 +1054,7 @@ window.Page_map = (() => {
|
|||
|
||||
// FAB umschalten
|
||||
const btn = document.getElementById('map-rec-btn');
|
||||
if (btn) { btn.textContent = '⏹'; btn.classList.add('recording'); }
|
||||
if (btn) { btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg>'; btn.classList.add('recording'); }
|
||||
|
||||
// Aufzeichnungs-Panel einblenden
|
||||
const panel = document.getElementById('map-rec-panel');
|
||||
|
|
@ -978,8 +1066,8 @@ window.Page_map = (() => {
|
|||
await _acquireWakeLock();
|
||||
const hint = document.getElementById('map-rec-hint');
|
||||
if (hint) hint.textContent = _wakeLock
|
||||
? '📵 Bildschirm bleibt aktiv — GPS läuft'
|
||||
: '⚠️ Bildschirm-Lock nicht unterstützt — Bildschirm aktiv lassen';
|
||||
? 'Bildschirm bleibt aktiv — GPS läuft'
|
||||
: 'Bildschirm-Lock nicht unterstützt — Bildschirm aktiv lassen';
|
||||
|
||||
// Sichtbarkeit: Wake Lock bei Tab-Wechsel neu anfordern
|
||||
document.addEventListener('visibilitychange', _onVisibilityChange);
|
||||
|
|
@ -1003,7 +1091,12 @@ window.Page_map = (() => {
|
|||
() => {},
|
||||
{ enableHighAccuracy: true, maximumAge: 0, timeout: 10000 }
|
||||
);
|
||||
UI.toast.success('Aufzeichnung gestartet — los geht\'s! 🐕');
|
||||
UI.toast.success('Aufzeichnung gestartet — los geht\'s!');
|
||||
|
||||
// Pocket-Modus aktivieren wenn in Einstellungen eingeschaltet
|
||||
if (localStorage.getItem('by_pocket_mode') === 'true') {
|
||||
setTimeout(_showPocketOverlay, 800); // kurz warten damit Toast sichtbar war
|
||||
}
|
||||
}
|
||||
|
||||
async function _onVisibilityChange() {
|
||||
|
|
@ -1074,6 +1167,7 @@ window.Page_map = (() => {
|
|||
if (distEl) distEl.textContent = _recDistKm.toFixed(2);
|
||||
if (timeEl) timeEl.textContent = `${mm}:${ss}`;
|
||||
if (paceEl) paceEl.textContent = pace;
|
||||
_updatePocketOverlay();
|
||||
}
|
||||
|
||||
function _stopRecording() {
|
||||
|
|
@ -1081,10 +1175,11 @@ window.Page_map = (() => {
|
|||
if (_recTimerInt) { clearInterval(_recTimerInt); _recTimerInt = null; }
|
||||
_recActive = false;
|
||||
_releaseWakeLock();
|
||||
_hidePocketOverlay();
|
||||
document.removeEventListener('visibilitychange', _onVisibilityChange);
|
||||
|
||||
const btn = document.getElementById('map-rec-btn');
|
||||
if (btn) { btn.textContent = '🔴'; btn.classList.remove('recording'); }
|
||||
if (btn) { btn.innerHTML = '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg>'; btn.classList.remove('recording'); }
|
||||
|
||||
const panel = document.getElementById('map-rec-panel');
|
||||
if (panel) panel.classList.remove('active', 'paused');
|
||||
|
|
@ -1100,16 +1195,34 @@ window.Page_map = (() => {
|
|||
_showRecSaveModal(_recTrack, _recDistKm, dauMin);
|
||||
}
|
||||
|
||||
async function _prefillRouteName(track, distKm) {
|
||||
const nameInput = document.querySelector('#rec-save-form [name="name"]');
|
||||
if (!nameInput || nameInput.value) return;
|
||||
const pt = track[0];
|
||||
const date = new Date().toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric' });
|
||||
const km = distKm.toFixed(1);
|
||||
let ort = '';
|
||||
try {
|
||||
const r = await fetch(`https://nominatim.openstreetmap.org/reverse?lat=${pt.lat}&lon=${pt.lon}&format=json&zoom=13&addressdetails=1&accept-language=de`, { cache: 'no-store' });
|
||||
const data = await r.json();
|
||||
const a = data.address || {};
|
||||
ort = a.village || a.town || a.suburb || a.city_district || a.city || a.municipality || '';
|
||||
} catch {}
|
||||
if (!nameInput.value) nameInput.value = ort
|
||||
? `Gassirunde ${ort} · ${date} · ${km} km`
|
||||
: `Gassirunde · ${date} · ${km} km`;
|
||||
}
|
||||
|
||||
function _showRecSaveModal(track, distKm, dauMin) {
|
||||
const body = `
|
||||
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
|
||||
🎉 ${track.length} GPS-Punkte · ${distKm.toFixed(2)} km · ca. ${dauMin} min
|
||||
${track.length} GPS-Punkte · ${distKm.toFixed(2)} km · ca. ${dauMin} min
|
||||
</p>
|
||||
<form id="rec-save-form" autocomplete="off">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name der Route *</label>
|
||||
<input class="form-control" type="text" name="name"
|
||||
placeholder="z.B. Waldspaziergang am See" required>
|
||||
placeholder="Wird automatisch ermittelt…" required>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
|
||||
<div class="form-group">
|
||||
|
|
@ -1124,34 +1237,34 @@ window.Page_map = (() => {
|
|||
<label class="form-label">Untergrund</label>
|
||||
<select class="form-control" name="untergrund">
|
||||
<option value="">– unbekannt –</option>
|
||||
<option value="wald">🌲 Wald</option>
|
||||
<option value="asphalt">🛣️ Asphalt</option>
|
||||
<option value="wiese">🌿 Wiese</option>
|
||||
<option value="mix">🔀 Mix</option>
|
||||
<option value="wald">Wald</option>
|
||||
<option value="asphalt">Asphalt</option>
|
||||
<option value="wiese">Wiese</option>
|
||||
<option value="mix">Mix</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Hundetauglichkeit</label>
|
||||
<div class="rk-paw-select" id="rec-paw-select">
|
||||
<button type="button" class="rk-paw-btn" data-val="eingeschränkt">🐾 Eingeschränkt</button>
|
||||
<button type="button" class="rk-paw-btn" data-val="gut">🐾🐾 Gut</button>
|
||||
<button type="button" class="rk-paw-btn selected" data-val="sehr_gut">🐾🐾🐾 Sehr gut</button>
|
||||
<button type="button" class="rk-paw-btn" data-val="premium">🐾🐾🐾🐾 Premium</button>
|
||||
<button type="button" class="rk-paw-btn" data-val="eingeschränkt"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Eingeschränkt</button>
|
||||
<button type="button" class="rk-paw-btn" data-val="gut"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Gut</button>
|
||||
<button type="button" class="rk-paw-btn selected" data-val="sehr_gut"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Sehr gut</button>
|
||||
<button type="button" class="rk-paw-btn" data-val="premium"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#paw-print"></use></svg> Premium</button>
|
||||
</div>
|
||||
<input type="hidden" name="hunde_tauglichkeit" id="rec-paw-val" value="sehr_gut">
|
||||
</div>
|
||||
<div class="form-group" style="display:flex;gap:var(--space-4)">
|
||||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" name="schatten"> 🌳 Viel Schatten
|
||||
<input type="checkbox" name="schatten"> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#tree"></use></svg> Viel Schatten
|
||||
</label>
|
||||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" name="leine_empfohlen"> 🔗 Leine empfohlen
|
||||
<input type="checkbox" name="leine_empfohlen"> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#tag"></use></svg> Leine empfohlen
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
|
||||
<input type="checkbox" name="is_public" checked> 🌍 Öffentlich (von allen sichtbar)
|
||||
<input type="checkbox" name="is_public" checked> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#globe"></use></svg> Öffentlich (von allen sichtbar)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
|
@ -1164,10 +1277,12 @@ window.Page_map = (() => {
|
|||
|
||||
const footer = `
|
||||
<button type="button" class="btn btn-secondary flex-1" id="rms-discard">Verwerfen</button>
|
||||
<button type="submit" form="rec-save-form" class="btn btn-primary flex-1">💾 Speichern</button>
|
||||
<button type="submit" form="rec-save-form" class="btn btn-primary flex-1"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#floppy-disk"></use></svg> Speichern</button>
|
||||
`;
|
||||
|
||||
UI.modal.open({ title: '🥾 Route benennen', body, footer });
|
||||
UI.modal.open({ title: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg> Route benennen', body, footer });
|
||||
|
||||
_prefillRouteName(track, distKm); // async, füllt Name-Feld sobald Nominatim antwortet
|
||||
|
||||
document.getElementById('rec-paw-select')?.addEventListener('click', e => {
|
||||
const btn = e.target.closest('.rk-paw-btn');
|
||||
|
|
@ -1204,7 +1319,7 @@ window.Page_map = (() => {
|
|||
UI.modal.close();
|
||||
if (_recPolyline) { _recPolyline.remove(); _recPolyline = null; }
|
||||
if (_recMarker) { _recMarker.remove(); _recMarker = null; }
|
||||
UI.toast.success(`Route „${saved.name}" gespeichert! 🎉`);
|
||||
UI.toast.success(`Route „${saved.name}" gespeichert!`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue