/** * De Ward PWA (FEP-633c), clientkant. Tegenhanger van guardian.js, maar dan * vanuit het kind: wie waakt over mij, is dat vangnet heel, en wat ligt er bij * hen te wachten. * * Geen framework, net als guardian.js: de toestand komt als JSON mee met de * pagina en dit bestand tekent hem. Alles wat nieuw is komt van /ward/api/*. */ 'use strict'; const state = JSON.parse(document.getElementById('ward-state').textContent); const $ = (id) => document.getElementById(id); /** * De §4-uitkomst in woorden. * * Drie gevallen, en het derde is het punt: "onbereikbaar" is GEEN goedkeuring. * Een ward wiens guardian-server plat ligt heeft een ONGECONTROLEERD vangnet, * en dat is iets anders dan een gecontroleerd vangnet. Niets tonen zou het * eerste als het tweede laten lezen. Zelfde drieslag als in Shaer. */ const CHECK_NOTE = { malformed: { text: 'Deze guardian heeft zelf guardians. Een noodoproep komt hier niet aan.', cls: 'alarm' }, unreachable: { text: 'Deze guardian was niet te bereiken, dus niet gecontroleerd.', cls: 'unknown' }, sound: null, }; /** Beschikbaarheid in de taal die iedereen zonder handleiding leest. */ const AVAILABILITY = { active: { label: 'Beschikbaar', cls: 'ok' }, away: { label: 'Even weg', cls: 'away' }, dormant: { label: 'Offline', cls: 'off' }, }; function el(tag, cls, text) { const n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; } function renderGuardians(checks) { const list = $('guardian-list'); list.replaceChildren(); $('guardians-empty').hidden = state.guardians.length > 0; state.guardians.forEach((g) => { const li = el('li', 'card'); const head = el('div', 'row'); head.append(el('span', 'name', g.handle)); const av = AVAILABILITY[g.availability] || AVAILABILITY.active; head.append(el('span', `chip ${av.cls}`, av.label)); li.append(head); // De controle staat WAAR de guardian genoemd wordt, niet in een logboek: // daar heeft het gat betekenis. const note = CHECK_NOTE[checks?.[g.uri]]; if (note) li.append(el('p', `note ${note.cls}`, note.text)); list.append(li); }); } function renderOffers() { const sec = $('offers'); const list = $('offer-list'); list.replaceChildren(); sec.hidden = !state.offers.length; state.offers.forEach((o) => { const li = el('li', 'card'); const who = o['shaer:candidate'] || o.actor || ''; li.append(el('p', 'name', `${who} wil jouw guardian worden`)); if (o['shaer:acceptedBy']) { li.append(el('p', 'hint', `${o['shaer:acceptedBy'].length} van de partijen zei al ja`)); } list.append(li); }); } function renderWaiting() { const sec = $('waiting'); const list = $('waiting-list'); list.replaceChildren(); sec.hidden = !state.waitingFollows.length; state.waitingFollows.forEach((w) => { const li = el('li', 'card'); li.append(el('p', 'name', w.handle)); // Niet "pending": de bal ligt bij iemand die je kent, en geweigerd is // helemaal geen wachten meer. Zelfde drie zinnen als in Shaer. const denied = w.status === 'denied'; li.append(el('p', denied ? 'note alarm' : 'hint', denied ? 'Je guardians hebben hier nee tegen gezegd.' : 'Je guardians moeten hier nog ja tegen zeggen.')); list.append(li); }); } /** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. */ async function loadChecks() { if (!state.guardians.length) return; try { const r = await fetch('/ward/api/guardian-checks', { headers: { Accept: 'application/json' } }); if (!r.ok) return; const { checks } = await r.json(); renderGuardians(checks); } catch { /* stilte is hier juist: dan blijft de lijst zonder oordeel staan */ } } function wireHelp() { const btn = $('help-btn'); const hint = $('help-hint'); if (!state.guardians.length) { btn.disabled = true; hint.textContent = 'Je hebt nog geen guardians om te vragen.'; return; } hint.textContent = `Gaat naar ${state.guardians.length} guardian(s).`; } renderGuardians(null); renderOffers(); renderWaiting(); wireHelp(); loadChecks();