diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/lazy.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/lazy.js | 40 |
1 files changed, 36 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/lazy.js b/packages/meshbay-hub/src/meshbay_hub/static/lazy.js index 30a564c..fda73c0 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/lazy.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/lazy.js @@ -9,17 +9,45 @@ // // A failed fetch (a network drop on a phone) is not remembered: the next time // the component is shown it asks again, rather than failing for the session. +// +// A failed fetch is also *said*, never left as the placeholder's spinner. The +// commonest failure is not the network: the hub serves the module graph under +// `/a/<hash>/` for the current hash only, so a tab opened before a hub deploy +// asks for every module it has not loaded yet under a prefix that now answers +// 404. Such a tab kept Files and Chat (already loaded) and spun for ever on +// Search, Videos, Music, Photos and the player — with nothing in the console, +// because the rejection was swallowed here. Asking again cannot help that tab; +// reloading the page is the one thing that does, so that is what is offered. +// Not done automatically: a reload that fails the same way would loop, and it +// would throw away whatever the reader had in progress. import { html, useState, useEffect } from './vendor/htm-preact.js'; +import { t } from './i18n.js'; const SPINNER = html`<p class="page-message"><span class="spinner"></span></p>`; -function lazy(load, name, placeholder = SPINNER) { +function LoadFailed({ onClose }) { + return html` + <div class="page-message lazy-failed" role="alert"> + <p>${t('lazy.load_failed')}</p> + <button class="admin-btn" onClick=${() => location.reload()}>${t('lazy.reload')}</button> + ${onClose && html` + ${' '}<button class="admin-btn" onClick=${onClose}>${t('admin.btn_close')}</button> + `} + </div> + `; +} + +// `frame` wraps the failure notice, so a component that is an overlay (the +// video player) can put it where the overlay would have been rather than +// somewhere at the bottom of the page, under nothing anyone is looking at. +function lazy(load, name, placeholder = SPINNER, frame = (content) => content) { let Loaded = null; let pending = null; function Lazy(props) { const [, setReady] = useState(Loaded !== null); + const [failed, setFailed] = useState(false); useEffect(() => { if (Loaded) return undefined; let alive = true; @@ -28,11 +56,15 @@ function lazy(load, name, placeholder = SPINNER) { (m) => { Loaded = m[name]; }, (e) => { pending = null; throw e; }); } - pending.then(() => { if (alive) setReady(true); }, () => {}); + pending.then(() => { if (alive) setReady(true); }, (e) => { + console.error(`[MeshBay] could not load ${name}:`, e); + if (alive) setFailed(true); + }); return () => { alive = false; }; }, []); - if (!Loaded) return placeholder; - return html`<${Loaded} ...${props} />`; + if (Loaded) return html`<${Loaded} ...${props} />`; + if (failed) return frame(html`<${LoadFailed} onClose=${props.onClose} />`); + return placeholder; } Lazy.displayName = `Lazy(${name})`; return Lazy; |