feat: sync first-task completion across devices (Closes #1148)
This commit is contained in:
1 parent
e2be14d7c7
commit
fa47ded3ab
7 files changed
+184
-12
No files matched your search
@@ -44,9 +44,16 @@
|
||||
|
||||
function store(value) {
|
||||
const currentKey = key();
|
||||
if (!currentKey) return;
|
||||
try { options.storage.setItem(currentKey, value); }
|
||||
catch (_error) {}
|
||||
const current = state();
|
||||
if (!currentKey || current === value || current === 'complete') return false;
|
||||
try {
|
||||
options.storage.setItem(currentKey, value);
|
||||
return true;
|
||||
} catch (_error) { return false; }
|
||||
}
|
||||
|
||||
function adoptRemote(value) {
|
||||
return ['coaching', 'complete'].includes(value) && store(value);
|
||||
}
|
||||
|
||||
function required() {
|
||||
@@ -95,6 +102,7 @@
|
||||
function completeOutcome() {
|
||||
if (state() !== 'coaching') return false;
|
||||
store('complete');
|
||||
options.eventTarget?.dispatchEvent?.(new CustomEvent('stackchain:first-task-complete'));
|
||||
renderCoach();
|
||||
if (options.receipt) {
|
||||
options.receipt.hidden = false;
|
||||
@@ -120,5 +128,5 @@
|
||||
options.eventTarget?.addEventListener('offline', render);
|
||||
}
|
||||
|
||||
return {required, open, refresh, render, start, completeOutcome};
|
||||
return {required, open, refresh, render, start, completeOutcome, adoptRemote};
|
||||
});
|
||||
+27
-5
@@ -61,12 +61,22 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
revision: plan.revision, ids: plan.ids,
|
||||
capacity_minutes: plan.capacity_minutes ?? null, estimates: plan.estimates || {},
|
||||
};
|
||||
if (['coaching', 'complete'].includes(plan.first_task_state)) {
|
||||
snapshot.first_task_state = plan.first_task_state;
|
||||
}
|
||||
if (plan.plan_date) {
|
||||
snapshot.plan_date = plan.plan_date;
|
||||
snapshot.timezone = plan.timezone || null;
|
||||
}
|
||||
try {
|
||||
storage?.setItem(snapshotKey(), JSON.stringify(snapshot));
|
||||
const login = String(getLogin?.() || '').trim().toLowerCase();
|
||||
const activationKey = login && 'stackchain.first-task.v1:' + login;
|
||||
const current = activationKey && storage?.getItem(activationKey);
|
||||
const rank = {'': 0, coaching: 1, complete: 2};
|
||||
if (activationKey && rank[snapshot.first_task_state] > (rank[current] || 0)) {
|
||||
storage?.setItem(activationKey, snapshot.first_task_state);
|
||||
}
|
||||
} catch (_error) {
|
||||
// A storage quota failure must not prevent the current tab from using server truth.
|
||||
}
|
||||
@@ -123,7 +133,7 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
}
|
||||
const operation = record?.operation;
|
||||
const valid = operation && typeof operation.operation_id === 'string' &&
|
||||
['add', 'remove', 'move', 'configure', 'rollover'].includes(operation.action) &&
|
||||
['add', 'remove', 'move', 'configure', 'rollover', 'activate'].includes(operation.action) &&
|
||||
typeof operation.item_id === 'string' && Number.isFinite(Number(record.queued_at));
|
||||
if (valid) records.push({ ...record, recordKey });
|
||||
else {
|
||||
@@ -151,7 +161,7 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
? record.operation.base_revision : Math.max(0, savedRevision()),
|
||||
}))
|
||||
.filter(operation => operation && typeof operation.operation_id === 'string' &&
|
||||
['add', 'remove', 'move', 'configure', 'rollover'].includes(operation.action) && typeof operation.item_id === 'string');
|
||||
['add', 'remove', 'move', 'configure', 'rollover', 'activate'].includes(operation.action) && typeof operation.item_id === 'string');
|
||||
} catch (_error) {
|
||||
return [];
|
||||
}
|
||||
@@ -176,7 +186,7 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
return Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);
|
||||
}
|
||||
|
||||
function enqueue(action, itemId, direction = null) {
|
||||
function enqueue(action, itemId, direction = null, fields = {}) {
|
||||
const operations = pending();
|
||||
if (action === 'remove' && operations.some(operation =>
|
||||
operation.action === 'remove' && operation.item_id === itemId
|
||||
@@ -186,7 +196,7 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
}
|
||||
const operation = {
|
||||
operation_id: operationId(), action, item_id: itemId, direction,
|
||||
base_revision: Math.max(0, savedRevision()),
|
||||
base_revision: Math.max(0, savedRevision()), ...fields,
|
||||
};
|
||||
const storageKey = key();
|
||||
if (!storageKey || !storage) return false;
|
||||
@@ -223,6 +233,15 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
}
|
||||
}
|
||||
|
||||
function enqueueActivation(state) {
|
||||
if (!['coaching', 'complete'].includes(state)) return false;
|
||||
const rank = {coaching: 1, complete: 2};
|
||||
const queued = pending().filter(operation => operation.action === 'activate');
|
||||
if (queued.some(operation => rank[operation.activation_state] >= rank[state])) return true;
|
||||
queued.forEach(operation => removeOperation(operation.operation_id));
|
||||
return enqueue('activate', 'first-task', null, {activation_state: state});
|
||||
}
|
||||
|
||||
function enqueueRollover(proposed) {
|
||||
if (!proposed || proposed.action !== 'rollover') return false;
|
||||
const operation = {
|
||||
@@ -323,6 +342,9 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
|
||||
function startLifecycle({ window: windowObject, document: documentObject }) {
|
||||
windowObject?.addEventListener?.('online', flush);
|
||||
windowObject?.addEventListener?.('stackchain:first-task-complete', () => {
|
||||
if (enqueueActivation('complete')) flush();
|
||||
});
|
||||
documentObject?.addEventListener?.('visibilitychange', () =>
|
||||
documentObject.hidden ? false : flush()
|
||||
);
|
||||
@@ -332,7 +354,7 @@ function createTodaySync({ storage, getLogin, fetchJson, onRemoteIds, onRemotePl
|
||||
if (change.queue === 'today' && pending().length) flush();
|
||||
});
|
||||
|
||||
return { enqueue, enqueueConfiguration, enqueueRollover, migrate, flush, pending, startLifecycle };
|
||||
return { enqueue, enqueueConfiguration, enqueueActivation, enqueueRollover, migrate, flush, pending, startLifecycle };
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = createTodaySync;
|
||||
Reference in new issue
Block a user