Session 2026-04-19: Navigation, Kompass, Übungsfortschritt
Routen-Navigation:
- POI-Marker: farbige Kreise mit Phosphor-Icons (wie Hauptkarte)
- Screensaver: Navi-Pfeil dreht sich via DeviceOrientationEvent (iOS+Android)
- Pfeil-Dämpfung: EMA α=0.12 mit Wrap-Around
- GPS-Distanz-Bug: Fortschritt nur wenn <500m zur Route
- fitBounds: User-Position nur wenn <20km von Route
- Screensaver: "zur Route" vs "verbleibend" kontextabhängig
- Richtungspfeile entlang Route (blau, max 7 Stück)
- Umkehren ins Route-Detail verschoben, Detail-Map rebuildet sich
- rk-header z-index:10 (Leaflet-Tiles liefen drüber)
- 2-Sek. Screensaver-Entsperrung
km-Tracking:
- route_walks Tabelle
- POST /api/routes/{id}/walked (≥50%)
- total_km = erstellte Routes + gelaufene route_walks
- Toast bei neuem Badge
Übungsfortschritt:
- exercise_progress + training_plan_progress Tabellen
- GET/POST /api/training/progress, /plan-progress, /suggestions
- uebungen.js: API-first + localStorage-Fallback + Auto-Migration
- Empfehlungs-Banner (regelbasiert)
- Toast bei "sitzt"
This commit is contained in:
parent
390176383f
commit
9a78121a3e
25 changed files with 2487 additions and 248 deletions
|
|
@ -34,25 +34,68 @@ window.Page_chat = (() => {
|
|||
// ----------------------------------------------------------
|
||||
// Conversation list
|
||||
// ----------------------------------------------------------
|
||||
const _isDesktop = () => window.innerWidth >= 768;
|
||||
|
||||
function _listPaneHTML() {
|
||||
return `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;
|
||||
padding:0 var(--space-4);height:56px;box-sizing:border-box;
|
||||
flex-shrink:0;border-bottom:1px solid var(--c-border);
|
||||
background:var(--c-surface)">
|
||||
<h2 style="font-size:var(--text-base);font-weight:var(--weight-bold);margin:0">Nachrichten</h2>
|
||||
<button class="btn btn-primary btn-sm" id="chat-new-btn">
|
||||
${UI.icon('pencil-simple')} Neue Nachricht
|
||||
</button>
|
||||
</div>
|
||||
<div id="chat-list-body" style="overflow-y:auto;flex:1"></div>`;
|
||||
}
|
||||
|
||||
async function _showList() {
|
||||
_view = 'list';
|
||||
_stopPolling();
|
||||
_convId = null;
|
||||
|
||||
_container.innerHTML = `
|
||||
<div style="background:var(--c-surface)">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;
|
||||
padding:var(--space-4) var(--space-4) var(--space-2)">
|
||||
<h2 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:0">Nachrichten</h2>
|
||||
<button class="btn btn-primary btn-sm" id="chat-new-btn">
|
||||
${UI.icon('pencil-simple')} Neue Nachricht
|
||||
</button>
|
||||
</div>
|
||||
<div id="chat-list-body"></div>
|
||||
</div>
|
||||
`;
|
||||
if (_isDesktop()) {
|
||||
// Split-Pane: linke Spalte bleibt, rechte zeigt Placeholder
|
||||
if (!document.getElementById('chat-split')) {
|
||||
_container.innerHTML = `
|
||||
<div id="chat-split" style="display:flex;flex:1;min-height:0;overflow:hidden;
|
||||
position:absolute;inset:0">
|
||||
<div id="chat-list-pane" style="width:320px;flex-shrink:0;display:flex;
|
||||
flex-direction:column;border-right:1px solid var(--c-border);
|
||||
background:var(--c-surface);min-height:0">
|
||||
${_listPaneHTML()}
|
||||
</div>
|
||||
<div id="chat-thread-pane" style="flex:1;min-width:0;display:flex;
|
||||
align-items:center;justify-content:center;
|
||||
background:var(--c-bg);color:var(--c-text-muted);font-size:var(--text-sm)">
|
||||
${UI.icon('chat-circle-dots')} Gespräch auswählen
|
||||
</div>
|
||||
</div>`;
|
||||
document.getElementById('chat-new-btn')?.addEventListener('click', _showNewMessagePicker);
|
||||
} else {
|
||||
// Split existiert — nur rechte Seite zurücksetzen
|
||||
const pane = document.getElementById('chat-thread-pane');
|
||||
if (pane) {
|
||||
pane.style.cssText = 'flex:1;display:flex;align-items:center;justify-content:center;background:var(--c-bg);color:var(--c-text-muted);font-size:var(--text-sm)';
|
||||
pane.innerHTML = `${UI.icon('chat-circle-dots')} Gespräch auswählen`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_container.innerHTML = `
|
||||
<div style="background:var(--c-surface)">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;
|
||||
padding:var(--space-4) var(--space-4) var(--space-2)">
|
||||
<h2 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:0">Nachrichten</h2>
|
||||
<button class="btn btn-primary btn-sm" id="chat-new-btn">
|
||||
${UI.icon('pencil-simple')} Neue Nachricht
|
||||
</button>
|
||||
</div>
|
||||
<div id="chat-list-body"></div>
|
||||
</div>`;
|
||||
document.getElementById('chat-new-btn')?.addEventListener('click', _showNewMessagePicker);
|
||||
}
|
||||
|
||||
document.getElementById('chat-new-btn')?.addEventListener('click', _showNewMessagePicker);
|
||||
await _loadList();
|
||||
await _updateChatBadge();
|
||||
}
|
||||
|
|
@ -122,12 +165,18 @@ window.Page_chat = (() => {
|
|||
_view = 'thread';
|
||||
_stopPolling();
|
||||
|
||||
_container.innerHTML = `
|
||||
// Aktive Markierung in der Liste
|
||||
document.querySelectorAll('.chat-conv-item').forEach(el =>
|
||||
el.classList.toggle('active', el.getAttribute('onclick')?.includes(String(convId)))
|
||||
);
|
||||
|
||||
const threadHTML = `
|
||||
<div class="chat-thread" id="chat-thread">
|
||||
<div class="chat-thread-header">
|
||||
${_isDesktop() ? '' : `
|
||||
<button class="btn btn-ghost btn-sm" onclick="Page_chat._showList()" style="padding:var(--space-1)">
|
||||
<svg class="ph-icon"><use href="/icons/phosphor.svg#arrow-left"></use></svg>
|
||||
</button>
|
||||
</button>`}
|
||||
<div style="position:relative;flex-shrink:0">
|
||||
<div class="chat-conv-avatar" id="chat-partner-av" style="width:32px;height:32px;font-size:var(--text-sm)">?</div>
|
||||
<span class="online-dot chat-avatar-dot" id="chat-partner-dot" style="display:none"></span>
|
||||
|
|
@ -154,6 +203,14 @@ window.Page_chat = (() => {
|
|||
</div>
|
||||
`;
|
||||
|
||||
const threadPane = document.getElementById('chat-thread-pane');
|
||||
if (_isDesktop() && threadPane) {
|
||||
threadPane.style.cssText = 'flex:1;min-width:0;display:flex;flex-direction:column';
|
||||
threadPane.innerHTML = threadHTML;
|
||||
} else {
|
||||
_container.innerHTML = threadHTML;
|
||||
}
|
||||
|
||||
// Auto-resize textarea
|
||||
const input = document.getElementById('chat-input');
|
||||
input.addEventListener('input', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue