feat: queue offline Today-to-Week moves (Closes #1233)
This commit is contained in:
1 parent
c87d56c939
commit
d63765a9b1
3 files changed
+130
-33
No files matched your search
@@ -17,29 +17,47 @@ function createTodayWeekReschedule({
|
||||
return login ? storagePrefix + encodeURIComponent(login) : '';
|
||||
}
|
||||
|
||||
function persist(record) {
|
||||
function validRecord(value) {
|
||||
return typeof value?.body?.operation_id === 'string' &&
|
||||
Array.isArray(value?.today?.ids) && Array.isArray(value?.week?.days);
|
||||
}
|
||||
|
||||
function readQueue() {
|
||||
const key = storageKey();
|
||||
if (!key || !storage) return [];
|
||||
try {
|
||||
const value = JSON.parse(storage.getItem(key) || 'null');
|
||||
if (Array.isArray(value?.records)) return value.records.filter(validRecord);
|
||||
return validRecord(value) ? [value] : [];
|
||||
} catch (_error) { return []; }
|
||||
}
|
||||
|
||||
function saveQueue(records) {
|
||||
const key = storageKey();
|
||||
if (!key || !storage) return false;
|
||||
try { storage.setItem(key, JSON.stringify(record)); return true; }
|
||||
try {
|
||||
if (!records.length) storage.removeItem(key);
|
||||
else storage.setItem(key, JSON.stringify({version:2, records}));
|
||||
return true;
|
||||
}
|
||||
catch (_error) { return false; }
|
||||
}
|
||||
|
||||
function persist(record) { return saveQueue([...readQueue(), record]); }
|
||||
|
||||
function pending() {
|
||||
const key = storageKey();
|
||||
if (!key || !storage) return null;
|
||||
try {
|
||||
const value = JSON.parse(storage.getItem(key) || 'null');
|
||||
return typeof value?.body?.operation_id === 'string' &&
|
||||
Array.isArray(value?.today?.ids) && Array.isArray(value?.week?.days) ? value : null;
|
||||
} catch (_error) { return null; }
|
||||
return readQueue()[0] || null;
|
||||
}
|
||||
|
||||
function resume() {
|
||||
const record = pending();
|
||||
const records = readQueue();
|
||||
const record = records[records.length - 1];
|
||||
if (!record) return null;
|
||||
adoptToday(record.today);
|
||||
week.adopt(record.week);
|
||||
return {...record, sync_pending:true};
|
||||
return {...record, sync_pending:true, pending_count:records.length};
|
||||
}
|
||||
|
||||
async function restoreConflict(error) {
|
||||
@@ -54,18 +72,29 @@ function createTodayWeekReschedule({
|
||||
|
||||
function flush() {
|
||||
if (flushing) return flushing;
|
||||
const record = pending();
|
||||
if (!record) return Promise.resolve(false);
|
||||
const key = storageKey();
|
||||
flushing = api('api/v1/week/reschedule', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(record.body),
|
||||
}).then(result => {
|
||||
adoptToday(result.today);
|
||||
week.adopt(result.week);
|
||||
const latest = pending();
|
||||
if (latest?.body?.operation_id === record.body.operation_id) storage.removeItem(key);
|
||||
return {...result, sync_pending:false};
|
||||
}).catch(restoreConflict).finally(() => { flushing = null; });
|
||||
if (!pending()) return Promise.resolve(false);
|
||||
flushing = (async () => {
|
||||
let result = null;
|
||||
while (true) {
|
||||
const records = readQueue();
|
||||
const record = records[0];
|
||||
if (!record) return result ? {...result, sync_pending:false, pending_count:0} : false;
|
||||
try {
|
||||
result = await api('api/v1/week/reschedule', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(record.body),
|
||||
});
|
||||
} catch (error) { await restoreConflict(error); }
|
||||
adoptToday(result.today);
|
||||
week.adopt(result.week);
|
||||
const latest = readQueue();
|
||||
if (latest[0]?.body?.operation_id !== record.body.operation_id) continue;
|
||||
const remaining = latest.slice(1).map((entry, index) => index ? entry : ({
|
||||
...entry,
|
||||
body:{...entry.body, today_revision:result.today.revision, week_revision:result.week.revision},
|
||||
}));
|
||||
if (!saveQueue(remaining)) throw new Error('Move synced, but its local queue could not be updated.');
|
||||
}
|
||||
})().finally(() => { flushing = null; });
|
||||
return flushing;
|
||||
}
|
||||
|
||||
@@ -92,16 +121,22 @@ function createTodayWeekReschedule({
|
||||
|
||||
async function open(identity) {
|
||||
let today;
|
||||
let offline = false;
|
||||
try { today = await api('api/v1/today'); }
|
||||
catch (error) {
|
||||
if (error?.status === 401 || error?.status === 403) throw error;
|
||||
today = getToday?.(); offline = true;
|
||||
const queued = readQueue().length;
|
||||
let offline = Boolean(queued);
|
||||
if (queued) {
|
||||
today = getToday?.();
|
||||
} else {
|
||||
try { today = await api('api/v1/today'); }
|
||||
catch (error) {
|
||||
if (error?.status === 401 || error?.status === 403) throw error;
|
||||
today = getToday?.(); offline = true;
|
||||
}
|
||||
}
|
||||
if (!identity || !today?.ids?.includes(identity)) {
|
||||
throw new Error('The active Today item changed. Reopen rescheduling.');
|
||||
}
|
||||
const loaded = await week.load();
|
||||
const loaded = queued ? week.state?.() : await week.load();
|
||||
if (!loaded) throw new Error('Reload Today before rescheduling another saved move.');
|
||||
if (loaded?.sync_pending) {
|
||||
throw new Error('Reconnect before rescheduling Today into Week Ahead.');
|
||||
}
|
||||
@@ -168,11 +203,11 @@ function createTodayWeekReschedule({
|
||||
}
|
||||
if (current.offline) {
|
||||
current = null;
|
||||
return {...optimistic, sync_pending:true};
|
||||
return {...optimistic, sync_pending:true, pending_count:readQueue().length};
|
||||
}
|
||||
let result;
|
||||
try {
|
||||
result = await api('api/v1/week/reschedule', {
|
||||
result = admitted ? await flush() : await api('api/v1/week/reschedule', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body),
|
||||
});
|
||||
} catch(error) {
|
||||
@@ -185,7 +220,6 @@ function createTodayWeekReschedule({
|
||||
}
|
||||
adoptToday(result.today);
|
||||
week.adopt(result.week);
|
||||
if (admitted && pending()?.body?.operation_id === body.operation_id) storage.removeItem(storageKey());
|
||||
current = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user