From e97bd744e9655a7c4bd92434abe420354f011676 Mon Sep 17 00:00:00 2001 From: rene Date: Sat, 18 Apr 2026 13:35:59 +0200 Subject: [PATCH] Feature: Kamera-Fotos im Tagebuch in Mediathek speichern anbieten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nach capture-Aufnahmen erscheint Button 'Zum Fotoalbum hinzufügen'. Nutzt Web Share API (iOS/Android) mit als Fallback. UI.saveToAlbum() als wiederverwendbare Utility in ui.js. SW by-v200, APP_VER 168. --- backend/static/js/app.js | 2 +- backend/static/js/pages/diary.js | 27 +++++++++++++++++++++++- backend/static/js/ui.js | 36 ++++++++++++++++++++++++++++++++ backend/static/sw.js | 2 +- 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 6e844cd..fb48468 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '167'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '168'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const App = (() => { diff --git a/backend/static/js/pages/diary.js b/backend/static/js/pages/diary.js index dabca72..fa11127 100644 --- a/backend/static/js/pages/diary.js +++ b/backend/static/js/pages/diary.js @@ -643,13 +643,37 @@ window.Page_diary = (() => { } } - mediaInput?.addEventListener('change', () => _showPreview(mediaInput.files[0])); + function _showAlbumBtn(file) { + // Vorherigen Button entfernen falls vorhanden + document.getElementById('diary-save-album-btn')?.remove(); + // Nur anbieten wenn Share-API File-Support hat ODER als Download-Fallback + const canShare = navigator.canShare && navigator.canShare({ files: [file] }); + const canDownload = true; // funktioniert immer als Fallback + if (!canShare && !canDownload) return; + const btn = document.createElement('button'); + btn.type = 'button'; + btn.id = 'diary-save-album-btn'; + btn.className = 'btn btn-secondary btn-sm'; + btn.style.cssText = 'display:flex;align-items:center;gap:var(--space-1);margin-top:var(--space-2);width:100%'; + btn.innerHTML = ` + ${canShare ? 'Zum Fotoalbum hinzufügen' : 'Foto herunterladen'}`; + btn.addEventListener('click', () => UI.saveToAlbum(file)); + previewWrap.after(btn); + } + + mediaInput?.addEventListener('change', () => { + _showPreview(mediaInput.files[0]); + // Kein Album-Button bei Mediathek-Picks (Fotos sind bereits dort) + document.getElementById('diary-save-album-btn')?.remove(); + }); cameraInput?.addEventListener('change', () => { // Auswahl in mediaInput spiegeln damit Submit-Handler nur einen Ort abfragt const dt = new DataTransfer(); if (cameraInput.files[0]) dt.items.add(cameraInput.files[0]); mediaInput.files = dt.files; _showPreview(cameraInput.files[0]); + // Album-Button nach Kamera-Aufnahme anzeigen + if (cameraInput.files[0]) _showAlbumBtn(cameraInput.files[0]); }); document.getElementById('diary-btn-camera') ?.addEventListener('click', () => cameraInput.click()); @@ -677,6 +701,7 @@ window.Page_diary = (() => { previewWrap.style.display = 'none'; photoPreview.src = ''; videoPreview.src = ''; mediaInput.value = ''; + document.getElementById('diary-save-album-btn')?.remove(); }); // "Entfernen"-Button löscht Medium direkt diff --git a/backend/static/js/ui.js b/backend/static/js/ui.js index 04e6246..2d8c1df 100644 --- a/backend/static/js/ui.js +++ b/backend/static/js/ui.js @@ -307,6 +307,41 @@ const UI = (() => { if (parseFloat(tip.style.left) > maxL) tip.style.left = maxL + 'px'; }); + // ---------------------------------------------------------- + // SAVE TO ALBUM — Foto/Video nach Kamera-Aufnahme in Mediathek + // anbieten. Nur bei capture-Aufnahmen aufrufen (nicht Galerie). + // Nutzt Web Share API wenn verfügbar, sonst Fallback. + // ---------------------------------------------------------- + async function saveToAlbum(file) { + if (!file) return; + + // Web Share API mit Datei-Support (iOS Safari 15+, Chrome Android) + if (navigator.canShare && navigator.canShare({ files: [file] })) { + try { + await navigator.share({ + files: [file], + title: file.name || 'Foto', + }); + } catch (err) { + // AbortError = User hat Sheet geschlossen — kein Fehler anzeigen + if (err?.name !== 'AbortError') { + console.warn('saveToAlbum share error:', err); + } + } + return; + } + + // Fallback: direkter Download per + const url = URL.createObjectURL(file); + const a = document.createElement('a'); + a.href = url; + a.download = file.name || ('foto_' + Date.now() + (file.type === 'video/mp4' ? '.mp4' : '.jpg')); + a.style.display = 'none'; + document.body.appendChild(a); + a.click(); + setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 2000); + } + // Öffentliche API return { toast, modal, @@ -316,6 +351,7 @@ const UI = (() => { setupPhotoPreview, scrollTop, skeleton, icon: _svgIcon, escape, help, + saveToAlbum, }; })(); diff --git a/backend/static/sw.js b/backend/static/sw.js index f0234d9..c26cbbc 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v199'; +const CACHE_VERSION = 'by-v200'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten