Fix queue list silent crash and missing uploaded status
Three fixes for queue list not updating: 1. Safe getBlob fallback - works even if old storage.js is cached by the service worker (no Storage.getBlob method) 2. Include 'uploaded' status in active filter - photos briefly in uploaded state (between upload and verify) were invisible 3. Wrap all interval refreshes in try/catch so one error doesn't silently kill all future updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
// NextSnap Service Worker
|
// NextSnap Service Worker
|
||||||
// Provides offline-first caching for the app shell
|
// Provides offline-first caching for the app shell
|
||||||
|
|
||||||
const CACHE_VERSION = 'nextsnap-v17';
|
const CACHE_VERSION = 'nextsnap-v18';
|
||||||
const APP_SHELL_CACHE = 'nextsnap-shell-v13';
|
const APP_SHELL_CACHE = 'nextsnap-shell-v14';
|
||||||
const RUNTIME_CACHE = 'nextsnap-runtime-v13';
|
const RUNTIME_CACHE = 'nextsnap-runtime-v14';
|
||||||
|
|
||||||
// Assets to cache on install
|
// Assets to cache on install
|
||||||
const APP_SHELL_ASSETS = [
|
const APP_SHELL_ASSETS = [
|
||||||
|
|||||||
@@ -383,6 +383,15 @@
|
|||||||
const currentUsername = '{{ username }}';
|
const currentUsername = '{{ username }}';
|
||||||
let deletePhotoId = null;
|
let deletePhotoId = null;
|
||||||
|
|
||||||
|
// Safe blob accessor — works even if old storage.js is cached without getBlob()
|
||||||
|
function safeGetBlob(photo) {
|
||||||
|
if (!photo || !photo.blob) return null;
|
||||||
|
if (typeof Storage.getBlob === 'function') return Storage.getBlob(photo);
|
||||||
|
if (photo.blob instanceof Blob) return photo.blob;
|
||||||
|
// ArrayBuffer stored by new storage.js but old storage.js loaded (no getBlob)
|
||||||
|
try { return new Blob([photo.blob], { type: 'image/jpeg' }); } catch (e) { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize storage and load queue
|
// Initialize storage and load queue
|
||||||
Storage.init().then(() => {
|
Storage.init().then(() => {
|
||||||
SyncEngine.init(currentUsername);
|
SyncEngine.init(currentUsername);
|
||||||
@@ -398,9 +407,11 @@ async function loadQueue() {
|
|||||||
.where('username').equals(currentUsername)
|
.where('username').equals(currentUsername)
|
||||||
.sortBy('timestamp');
|
.sortBy('timestamp');
|
||||||
|
|
||||||
// Split into active (pending/uploading) and completed (verified/failed)
|
// Split into active and completed
|
||||||
const activePhotos = photos.filter(p => p.status === 'pending' || p.status === 'uploading');
|
const activePhotos = photos.filter(p =>
|
||||||
const completedPhotos = photos.filter(p => p.status === 'verified' || p.status === 'failed');
|
p.status === 'pending' || p.status === 'uploading' || p.status === 'uploaded');
|
||||||
|
const completedPhotos = photos.filter(p =>
|
||||||
|
p.status === 'verified' || p.status === 'failed');
|
||||||
|
|
||||||
if (photos.length === 0) {
|
if (photos.length === 0) {
|
||||||
emptyState.style.display = 'block';
|
emptyState.style.display = 'block';
|
||||||
@@ -446,7 +457,7 @@ function createQueueItem(photo, isCompleted) {
|
|||||||
item.classList.add('completed');
|
item.classList.add('completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (photo.status === 'uploading') {
|
if (photo.status === 'uploading' || photo.status === 'uploaded') {
|
||||||
item.classList.add('uploading');
|
item.classList.add('uploading');
|
||||||
} else if (photo.status === 'verified') {
|
} else if (photo.status === 'verified') {
|
||||||
item.classList.add('verified');
|
item.classList.add('verified');
|
||||||
@@ -457,7 +468,7 @@ function createQueueItem(photo, isCompleted) {
|
|||||||
// Create thumbnail (use placeholder if blob was stripped)
|
// Create thumbnail (use placeholder if blob was stripped)
|
||||||
const thumbnail = document.createElement('div');
|
const thumbnail = document.createElement('div');
|
||||||
thumbnail.className = 'queue-item-thumbnail';
|
thumbnail.className = 'queue-item-thumbnail';
|
||||||
const photoBlob = Storage.getBlob(photo);
|
const photoBlob = safeGetBlob(photo);
|
||||||
if (photoBlob && photoBlob.size > 0) {
|
if (photoBlob && photoBlob.size > 0) {
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = URL.createObjectURL(photoBlob);
|
img.src = URL.createObjectURL(photoBlob);
|
||||||
@@ -558,7 +569,7 @@ async function updateStats() {
|
|||||||
const pendingCount = photos.filter(p => p.status === 'pending').length;
|
const pendingCount = photos.filter(p => p.status === 'pending').length;
|
||||||
const uploadingCount = photos.filter(p => p.status === 'uploading').length;
|
const uploadingCount = photos.filter(p => p.status === 'uploading').length;
|
||||||
const totalSize = photos
|
const totalSize = photos
|
||||||
.map(p => Storage.getBlob(p))
|
.map(p => safeGetBlob(p))
|
||||||
.filter(b => b && b.size)
|
.filter(b => b && b.size)
|
||||||
.reduce((sum, b) => sum + b.size, 0) / 1024 / 1024;
|
.reduce((sum, b) => sum + b.size, 0) / 1024 / 1024;
|
||||||
|
|
||||||
@@ -621,25 +632,24 @@ function scheduleRefresh() {
|
|||||||
if (refreshTimer) clearTimeout(refreshTimer);
|
if (refreshTimer) clearTimeout(refreshTimer);
|
||||||
refreshTimer = setTimeout(() => {
|
refreshTimer = setTimeout(() => {
|
||||||
refreshTimer = null;
|
refreshTimer = null;
|
||||||
loadQueue();
|
refreshUI();
|
||||||
updateStats();
|
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
window.addEventListener('photo-updated', scheduleRefresh);
|
window.addEventListener('photo-updated', scheduleRefresh);
|
||||||
|
|
||||||
|
// Safe refresh wrapper — prevents silent failures from killing updates
|
||||||
|
async function refreshUI() {
|
||||||
|
try { await loadQueue(); } catch (e) { console.error('[QUEUE] loadQueue error:', e); }
|
||||||
|
try { await updateStats(); } catch (e) { console.error('[QUEUE] updateStats error:', e); }
|
||||||
|
}
|
||||||
|
|
||||||
// Poll every 3s while uploads are active, 15s when idle
|
// Poll every 3s while uploads are active, 15s when idle
|
||||||
let hasActiveUploads = true; // assume active until first check
|
let hasActiveUploads = true; // assume active until first check
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (hasActiveUploads) {
|
if (hasActiveUploads) refreshUI();
|
||||||
loadQueue();
|
|
||||||
updateStats();
|
|
||||||
}
|
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
// Slower fallback poll for idle state
|
// Slower fallback poll for idle state
|
||||||
setInterval(() => {
|
setInterval(refreshUI, 15000);
|
||||||
loadQueue();
|
|
||||||
updateStats();
|
|
||||||
}, 15000);
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user