Upload via GUI (10 Dateien)
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
// offline-store.js — the tablet's local data store (IndexedDB), so the app keeps
|
||||
// working with zero connectivity once the directory/an animal has been opened while
|
||||
// still on the practice WiFi. Two jobs: (1) cache what's been read, so it can be
|
||||
// re-displayed offline; (2) queue what's been written while offline, so it can be
|
||||
// replayed against the practice PC once reachable again.
|
||||
"use strict";
|
||||
|
||||
(function () {
|
||||
const DB_NAME = "fellakte-offline";
|
||||
const DB_VERSION = 1;
|
||||
let dbPromise = null;
|
||||
|
||||
function openDb() {
|
||||
if (dbPromise) return dbPromise;
|
||||
dbPromise = new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
req.onupgradeneeded = () => {
|
||||
const db = req.result;
|
||||
if (!db.objectStoreNames.contains("bundles")) db.createObjectStore("bundles", { keyPath: "id" });
|
||||
if (!db.objectStoreNames.contains("meta")) db.createObjectStore("meta", { keyPath: "key" });
|
||||
if (!db.objectStoreNames.contains("queue")) db.createObjectStore("queue", { keyPath: "id", autoIncrement: true });
|
||||
};
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
return dbPromise;
|
||||
}
|
||||
|
||||
// Runs a write (put/add/delete) against `name` and resolves once the transaction
|
||||
// commits. The return value of `fn` (an IDBRequest) is not needed by any caller.
|
||||
async function runWrite(name, fn) {
|
||||
const db = await openDb();
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = db.transaction(name, "readwrite");
|
||||
fn(t.objectStore(name));
|
||||
t.oncomplete = () => resolve();
|
||||
t.onerror = () => reject(t.error);
|
||||
});
|
||||
}
|
||||
// Runs a single read request against `name` and resolves with its result
|
||||
// (undefined on error, so callers can treat a miss the same as "not cached").
|
||||
async function runRead(name, fn) {
|
||||
const db = await openDb();
|
||||
return new Promise((resolve) => {
|
||||
const t = db.transaction(name, "readonly");
|
||||
const req = fn(t.objectStore(name));
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
window.offlineStore = {
|
||||
async saveDirectory(list) {
|
||||
await runWrite("meta", (s) => s.put({ key: "directory", value: list, at: Date.now() }));
|
||||
},
|
||||
async getDirectory() {
|
||||
const row = await runRead("meta", (s) => s.get("directory"));
|
||||
return row ? row.value : null;
|
||||
},
|
||||
|
||||
async saveBundle(bundle) {
|
||||
if (!bundle || !bundle.animal) return;
|
||||
await runWrite("bundles", (s) => s.put({ id: bundle.animal.id, bundle, at: Date.now() }));
|
||||
},
|
||||
async getBundle(id) {
|
||||
const row = await runRead("bundles", (s) => s.get(id));
|
||||
return row ? row.bundle : null;
|
||||
},
|
||||
// Scans cached bundles for one containing a finding with this id — used for
|
||||
// update/delete offline, where the caller only has the finding id, not the
|
||||
// animal it belongs to (same shape as the desktop IPC calls).
|
||||
async findAnimalIdForFinding(findingId) {
|
||||
const rows = (await runRead("bundles", (s) => s.getAll())) || [];
|
||||
const hit = rows.find((r) => r.bundle.findings.some((f) => f.id === findingId));
|
||||
return hit ? hit.id : null;
|
||||
},
|
||||
|
||||
async enqueue(op) {
|
||||
await runWrite("queue", (s) => s.add({ ...op, createdAt: Date.now() }));
|
||||
},
|
||||
async listQueue() {
|
||||
return (await runRead("queue", (s) => s.getAll())) || [];
|
||||
},
|
||||
async removeFromQueue(id) {
|
||||
await runWrite("queue", (s) => s.delete(id));
|
||||
},
|
||||
async queueCount() {
|
||||
return (await this.listQueue()).length;
|
||||
},
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user