diff --git a/app.js b/app.js index 808949e..28445ee 100644 --- a/app.js +++ b/app.js @@ -907,8 +907,20 @@ router.on('/orders/:id', async (args) => { document.getElementById('btn-pick-photo').onclick = () => galInput.click(); async function handleFiles(files) { - for (const f of files) { - await uploadPhoto(args.id, f); + // Screen wachhalten, solange die Auswahl abgearbeitet wird — sonst pausiert + // das Verkleinern mitten in der Serie (siehe resizeImage). + await offline.acquireWakeLock(); + try { + for (const f of files) { + // Ein einzelnes Foto darf die restliche Auswahl nicht blockieren + try { + await uploadPhoto(args.id, f); + } catch (e) { + showToast('Foto ' + (f.name || '') + ' fehlgeschlagen: ' + ((e && e.message) || ''), 'error'); + } + } + } finally { + offline.releaseWakeLock(); } // Nach Upload einfach die Route neu rendern — so werden Select-Mode, // Click-Handler und Thumbnails sauber neu aufgebaut. @@ -935,8 +947,15 @@ async function uploadPhoto(orderId, file) { await offline.syncQueue(); } -async function resizeImage(file, maxSide) { - return new Promise((resolve) => { +async function resizeImage(file, maxSide, timeoutMs) { + // SCHUTZZEIT (v1.1): Schaltet sich das Display waehrend einer Serie ab, pausiert der + // Browser Bild-Decoding und Canvas — dann feuert weder onload noch onerror und das + // Promise loeste NIE auf. Das passiert VOR dem Sichern in der Queue, die betroffenen + // Fotos waren also nirgends gespeichert, und weil die Auswahl sequentiell laeuft, + // stand die komplette restliche Auswahl still (28.08.2026, Auftrag PROV105: 8 von 19 + // Fotos kamen nie an und waren auch nicht in der Queue zum Nachholen). + // Im Zweifel lieber das unverkleinerte Original sichern als gar nichts. + const shrink = new Promise((resolve) => { const img = new Image(); const url = URL.createObjectURL(file); img.onload = () => { @@ -952,6 +971,8 @@ async function resizeImage(file, maxSide) { img.onerror = () => { URL.revokeObjectURL(url); resolve(file); }; img.src = url; }); + const guard = new Promise((resolve) => setTimeout(() => resolve(file), timeoutMs || 15000)); + return Promise.race([shrink, guard]); } /** diff --git a/lib/offline.js b/lib/offline.js index e9512cc..98df374 100644 --- a/lib/offline.js +++ b/lib/offline.js @@ -56,9 +56,14 @@ // Serie bleibt liegen (real am 28.08.2026: halbe Serie kam nicht an, der zweite Anlauf // erzeugte dann Duplikate). Scheitert die Anforderung (Akkusparmodus, alter Browser), // laeuft der Sync unveraendert weiter — die Queue faengt den Abbruch ohnehin ab. + // Referenzzaehler, weil zwei Stellen den Lock brauchen und sich verschachteln: + // die Auswahl-Schleife (Verkleinern + Sichern) und der Sync selbst. Wer zuerst + // fertig ist, darf dem anderen den Lock nicht wegnehmen. let wakeLock = null; + let wakeRefs = 0; async function acquireWakeLock() { + wakeRefs++; try { if (!navigator.wakeLock || wakeLock) return; wakeLock = await navigator.wakeLock.request('screen'); @@ -67,6 +72,8 @@ } function releaseWakeLock() { + wakeRefs = Math.max(0, wakeRefs - 1); + if (wakeRefs > 0) return; try { if (wakeLock) wakeLock.release(); } catch (_) {} wakeLock = null; } @@ -75,7 +82,10 @@ // App-Wechsel). Kommt sie zurueck und laeuft noch ein Sync, neu anfordern. try { document.addEventListener('visibilitychange', function () { - if (document.visibilityState === 'visible' && syncing) acquireWakeLock(); + if (document.visibilityState === 'visible' && wakeRefs > 0 && !wakeLock) { + wakeRefs--; // acquireWakeLock zaehlt gleich wieder hoch + acquireWakeLock(); + } }); } catch (_) {} @@ -203,5 +213,6 @@ setInterval(() => { if (navigator.onLine) syncQueue(); }, 15000); }); - window.offline = { enqueuePhoto, syncQueue, updateBadge, queueCount, listQueue, retryFailed }; + window.offline = { enqueuePhoto, syncQueue, updateBadge, queueCount, listQueue, retryFailed, + acquireWakeLock, releaseWakeLock }; })();