| 1 | // fixtures/status.mjs — the editor's save line and its load-error line, |
| 2 | // factored out of editor-entry.js so the rules are testable under plain |
| 3 | // Node (status.test.mjs). |
| 4 | // |
| 5 | // The save line is estilo's savestatus (the template renders it with |
| 6 | // every state's words from the catalog; its module, served at |
| 7 | // static/estilo/save-status.<hash>.mjs, moves it between them). Its |
| 8 | // four states: `saved`, `saving`, `retrying` (a save is being retried — |
| 9 | // the network, or a 5xx) and `failed` (a batch was DROPPED: the server |
| 10 | // refused it with a 4xx, and the person's Y.Doc now holds changes the |
| 11 | // server never will). The last two are different words on purpose: |
| 12 | // "Offline, retrying" on a batch nobody is retrying tells a person to |
| 13 | // wait for something that will not happen. |
| 14 | // |
| 15 | // A slide that would not load is not a save state — savestatus has no |
| 16 | // word for it — so it is its own line ([slides-load-error]), said until |
| 17 | // a slide does load, and independent of whatever the save line says. |
| 18 | |
| 19 | /** The save queue's status word (save-queue.mjs) as a savestatus state. */ |
| 20 | export function queueStatusState (state) { |
| 21 | if (state === 'ok') return 'saved' |
| 22 | if (state === 'failed') return 'failed' |
| 23 | return 'retrying' |
| 24 | } |
| 25 | |
| 26 | /** |
| 27 | * The state to show. A dropped batch outranks everything — it is the |
| 28 | * one fact the person has to act on — and stays for the session; then |
| 29 | * whatever the queue says. |
| 30 | */ |
| 31 | export function statusState ({ queueState = 'saved', sessionFailed = false } = {}) { |
| 32 | if (sessionFailed) return 'failed' |
| 33 | return queueState |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * The editor's status: setSave is savestatus's setter, errorEl the |
| 38 | * load-error line, errorText its words. Returns {save, drop, |
| 39 | * loadFailed}: save(state) records the queue's state (or re-shows the |
| 40 | * current one with no argument), drop() marks the session failed, |
| 41 | * loadFailed(bool) says or clears the load error. |
| 42 | */ |
| 43 | export function createEditorStatus ({ setSave, errorEl, errorText }) { |
| 44 | let queueState = 'saved' |
| 45 | let sessionFailed = false |
| 46 | function save (state) { |
| 47 | if (state) queueState = state |
| 48 | setSave(statusState({ queueState, sessionFailed })) |
| 49 | } |
| 50 | return { |
| 51 | save, |
| 52 | drop () { sessionFailed = true; save() }, |
| 53 | loadFailed (failed) { if (errorEl) errorEl.textContent = failed ? (errorText || '') : '' } |
| 54 | } |
| 55 | } |
| 56 | |
| 57 | /** |
| 58 | * Loads estilo's savestatus module (its fingerprinted URL is on the |
| 59 | * root as data-save-status-module) and mounts it on the line. A module |
| 60 | * that will not load leaves the load-error line saying so and rejects: |
| 61 | * an editor whose save line cannot move is one that would say "Saved" |
| 62 | * over changes it never saved. |
| 63 | */ |
| 64 | export async function mountSaveStatus (root, load = src => import(src)) { |
| 65 | const errorEl = root.querySelector('[slides-load-error]') |
| 66 | const errorText = root.dataset.textError |
| 67 | try { |
| 68 | const { mount } = await load(root.dataset.saveStatusModule) |
| 69 | return createEditorStatus({ setSave: mount(root.querySelector('[estilo-save-status]')), errorEl, errorText }) |
| 70 | } catch (err) { |
| 71 | if (errorEl) errorEl.textContent = errorText || '' |
| 72 | throw err |
| 73 | } |
| 74 | } |
| 75 | |