Add NextSnap PWA with photo gallery viewer and continuous capture
Offline-first photo capture app for Nextcloud with: - Camera capture with continuous mode (auto-reopens after each photo) - File browser with fullscreen image gallery, swipe navigation, and rename - Upload queue with background sync engine - Admin panel for Nextcloud user management - Service worker for offline-first caching (v13) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
152
app/static/js/storage.js
Normal file
152
app/static/js/storage.js
Normal file
@@ -0,0 +1,152 @@
|
||||
// NextSnap - IndexedDB Storage using Dexie.js
|
||||
'use strict';
|
||||
|
||||
const Storage = {
|
||||
db: null,
|
||||
|
||||
init() {
|
||||
// Initialize Dexie database
|
||||
this.db = new Dexie('nextsnap');
|
||||
|
||||
// Define schema with compound indexes
|
||||
this.db.version(1).stores({
|
||||
photos: '++id, username, timestamp, filename, targetPath, status, [username+status], [username+timestamp]',
|
||||
settings: '++id, username, [username+key]'
|
||||
});
|
||||
|
||||
return this.db.open();
|
||||
},
|
||||
|
||||
async savePhoto(photoData) {
|
||||
/**
|
||||
* Save a photo to IndexedDB
|
||||
* photoData: {
|
||||
* username: string,
|
||||
* timestamp: number,
|
||||
* filename: string,
|
||||
* targetPath: string,
|
||||
* blob: Blob,
|
||||
* status: 'pending' | 'uploading' | 'uploaded' | 'verified'
|
||||
* retryCount: number,
|
||||
* lastError: string
|
||||
* }
|
||||
*/
|
||||
const id = await this.db.photos.add({
|
||||
username: photoData.username,
|
||||
timestamp: photoData.timestamp,
|
||||
filename: photoData.filename,
|
||||
targetPath: photoData.targetPath,
|
||||
blob: photoData.blob,
|
||||
status: photoData.status || 'pending',
|
||||
retryCount: photoData.retryCount || 0,
|
||||
lastError: photoData.lastError || null
|
||||
});
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
async getPhoto(id) {
|
||||
return await this.db.photos.get(id);
|
||||
},
|
||||
|
||||
async getAllPhotos(username = null) {
|
||||
if (username) {
|
||||
return await this.db.photos
|
||||
.where('username').equals(username)
|
||||
.reverse()
|
||||
.sortBy('timestamp');
|
||||
}
|
||||
return await this.db.photos.reverse().sortBy('timestamp');
|
||||
},
|
||||
|
||||
async getPendingPhotos(username = null) {
|
||||
if (username) {
|
||||
return await this.db.photos
|
||||
.where('[username+status]')
|
||||
.equals([username, 'pending'])
|
||||
.sortBy('timestamp');
|
||||
}
|
||||
return await this.db.photos
|
||||
.where('status').equals('pending')
|
||||
.sortBy('timestamp');
|
||||
},
|
||||
|
||||
async getRecentPhotos(username, limit = 5) {
|
||||
return await this.db.photos
|
||||
.where('username').equals(username)
|
||||
.reverse()
|
||||
.limit(limit)
|
||||
.sortBy('timestamp');
|
||||
},
|
||||
|
||||
async updatePhoto(id, updates) {
|
||||
return await this.db.photos.update(id, updates);
|
||||
},
|
||||
|
||||
async deletePhoto(id) {
|
||||
return await this.db.photos.delete(id);
|
||||
},
|
||||
|
||||
async getPhotoCount(username = null, status = null) {
|
||||
let collection = this.db.photos;
|
||||
|
||||
if (username && status) {
|
||||
return await collection
|
||||
.where('[username+status]')
|
||||
.equals([username, status])
|
||||
.count();
|
||||
} else if (username) {
|
||||
return await collection
|
||||
.where('username').equals(username)
|
||||
.count();
|
||||
} else if (status) {
|
||||
return await collection
|
||||
.where('status').equals(status)
|
||||
.count();
|
||||
}
|
||||
|
||||
return await collection.count();
|
||||
},
|
||||
|
||||
async saveSetting(username, key, value) {
|
||||
// Check if setting exists
|
||||
const existing = await this.db.settings
|
||||
.where('[username+key]')
|
||||
.equals([username, key])
|
||||
.first();
|
||||
|
||||
if (existing) {
|
||||
await this.db.settings.update(existing.id, { value: value });
|
||||
} else {
|
||||
await this.db.settings.add({
|
||||
username: username,
|
||||
key: key,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async getSetting(username, key, defaultValue = null) {
|
||||
const setting = await this.db.settings
|
||||
.where('[username+key]')
|
||||
.equals([username, key])
|
||||
.first();
|
||||
|
||||
return setting ? setting.value : defaultValue;
|
||||
},
|
||||
|
||||
async clearVerifiedPhotos(username = null) {
|
||||
if (username) {
|
||||
return await this.db.photos
|
||||
.where('[username+status]')
|
||||
.equals([username, 'verified'])
|
||||
.delete();
|
||||
}
|
||||
return await this.db.photos
|
||||
.where('status').equals('verified')
|
||||
.delete();
|
||||
}
|
||||
};
|
||||
|
||||
// Make Storage globally available
|
||||
window.Storage = Storage;
|
||||
Reference in New Issue
Block a user