112 lines
3.5 KiB
JavaScript
112 lines
3.5 KiB
JavaScript
async function loadWorkspace({
|
|
document,
|
|
window = null,
|
|
createLoader = createFeatureLoader,
|
|
schedule = callback => setTimeout(callback, 750),
|
|
}) {
|
|
let cameOnline = false;
|
|
let replayed = false;
|
|
|
|
const status = document.querySelector('#my-work-action-status');
|
|
const retryButton = document.querySelector('#retry-workspace');
|
|
const names = ['work-core', 'today-timer', 'planning'];
|
|
const urls = Object.fromEntries(names.map(name => [name,
|
|
document.querySelector(`meta[name="stackchain-feature-${name}"]`)?.content || ''
|
|
]));
|
|
const originalUrls = {...urls};
|
|
const loader = createLoader({document, urls});
|
|
const attempts = Object.fromEntries(names.map(name => [name, 0]));
|
|
const failed = new Set();
|
|
const recoveries = new Map();
|
|
let retryInFlight = null;
|
|
|
|
const loadFeature = name => {
|
|
attempts[name] += 1;
|
|
urls[name] = originalUrls[name] + (attempts[name] > 1 ? '?retry=' + attempts[name] : '');
|
|
return loader.load(name);
|
|
};
|
|
const retryOnce = async name => {
|
|
try { return await loadFeature(name); }
|
|
catch (_error) {
|
|
await new Promise(resolve => schedule(resolve));
|
|
return loadFeature(name);
|
|
}
|
|
};
|
|
const showRecovery = () => {
|
|
if (status) status.textContent = failed.has('work-core') ?
|
|
'Workspace unavailable. Reconnect or retry.' :
|
|
'Today or planning tools unavailable. My Work is ready; reconnect or retry.';
|
|
if (retryButton) retryButton.hidden = retryButton.disabled = false;
|
|
};
|
|
const hideRecovery = () => {
|
|
if (failed.size) return;
|
|
if (retryButton) retryButton.hidden = true;
|
|
if (status) status.textContent = '';
|
|
};
|
|
const retryFailed = () => {
|
|
if (retryInFlight) return retryInFlight;
|
|
if (retryButton) retryButton.disabled = true;
|
|
const pending = Array.from(failed);
|
|
retryInFlight = Promise.all(pending.map(async name => {
|
|
try {
|
|
await loadFeature(name);
|
|
failed.delete(name);
|
|
recoveries.get(name)?.resolve(true);
|
|
recoveries.delete(name);
|
|
} catch (_error) {}
|
|
})).then(() => {
|
|
if (failed.size) showRecovery();
|
|
else hideRecovery();
|
|
}).finally(() => { retryInFlight = null; });
|
|
return retryInFlight;
|
|
};
|
|
retryButton?.addEventListener?.('click', retryFailed);
|
|
const handleOnline = () => { cameOnline = true; void retryFailed(); };
|
|
window?.addEventListener('online', handleOnline);
|
|
|
|
try {
|
|
await retryOnce('work-core');
|
|
} catch (_error) {
|
|
failed.add('work-core');
|
|
showRecovery();
|
|
await new Promise(resolve => recoveries.set('work-core', {resolve}));
|
|
}
|
|
hideRecovery();
|
|
|
|
const optional = ['today-timer', 'planning'].map(async name => {
|
|
try {
|
|
await retryOnce(name);
|
|
return true;
|
|
} catch (_error) {
|
|
failed.add(name);
|
|
showRecovery();
|
|
return new Promise(resolve => recoveries.set(name, {resolve}));
|
|
}
|
|
});
|
|
const optionalReady = Promise.all(optional).then(() => {
|
|
hideRecovery();
|
|
return true;
|
|
});
|
|
|
|
return {
|
|
optionalReady,
|
|
retryFeature(name) {
|
|
if (!failed.has(name)) return Promise.resolve(true);
|
|
return retryFailed().then(() => !failed.has(name));
|
|
},
|
|
replayOnline(callback) {
|
|
if (replayed) return;
|
|
replayed = true;
|
|
window?.removeEventListener('online', handleOnline);
|
|
if (cameOnline) callback();
|
|
},
|
|
};
|
|
}
|
|
|
|
if (typeof window !== 'undefined' && typeof document !== 'undefined' &&
|
|
typeof createFeatureLoader === 'function') {
|
|
window.stackchainWorkspaceLifecycle = loadWorkspace({document, window});
|
|
}
|
|
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = loadWorkspace;
|