source: Klonkt/src/assets/js/ward.js@ 303bfc4

Last change on this file since 303bfc4 was 303bfc4, checked in by Robin <roboburr@…>, 3 months ago

De Ward PWA: het vangnet vanuit het kind gezien

Klonkt had /guardian, de kant van de volwassene. Dit is dezelfde relatie
vanaf de andere kant: wie waakt er over mij, is dat vangnet heel, en wat
ligt er bij hen te wachten. Shaer doet dit als native app; dit is het in
de browser, voor wie geen app kan of wil installeren.

Plak 1 is bewust HET VANGNET en niet de sociale kant. De feed en de
composer staan al op de gewone Klonkt-site; die dubbelen we niet zomaar.
Wat Shaer uniek toevoegt voor een ward is juist het guardianship-deel.

Drie keuzes die er toe doen. De hulpknop staat bovenaan en vol breed: als
je hem nodig hebt moet je niet hoeven zoeken. De par. 4-controle zit in
een eigen endpoint omdat hij per guardian een remote fetch kost, dus de
pagina tekent eerst en vult daarna aan; een trage guardian-server mag het
scherm van een kind niet ophouden. En dezelfde drieslag als in de apps:
misvormd is oranje, onbereikbaar is grijs, in orde is stil. Onbereikbaar
is geen goedkeuring, maar ook geen alarm.

New file:
src/routes/ward.js

  • pagina, /api/state, /api/guardian-checks, assets, manifest, icoon

src/views/pages/ward.ejs

  • schil; begintoestand als JSON mee, geen inline script (CSP)

src/assets/js/ward.js

  • tekent guardians, aanvragen en wachtende follows

src/assets/css/ward.css

  • donker, grote raakzones

test/ward-pwa.test.js

  • de poort (een vreemde komt er niet in) en de inhoud

Changed files:
src/server.js

  • /ward gemonteerd

src/services/i18n.js

  • ward-sleutels in nl, en, de

remarks: 886 tests groen. De hulpknop en het accepteren van aanvragen
doen nog niets; dat is de volgende plak.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

  • Property mode set to 100644
File size: 4.1 KB
Line 
1/**
2 * De Ward PWA (FEP-633c), clientkant. Tegenhanger van guardian.js, maar dan
3 * vanuit het kind: wie waakt over mij, is dat vangnet heel, en wat ligt er bij
4 * hen te wachten.
5 *
6 * Geen framework, net als guardian.js: de toestand komt als JSON mee met de
7 * pagina en dit bestand tekent hem. Alles wat nieuw is komt van /ward/api/*.
8 */
9'use strict';
10
11const state = JSON.parse(document.getElementById('ward-state').textContent);
12const $ = (id) => document.getElementById(id);
13
14/**
15 * De §4-uitkomst in woorden.
16 *
17 * Drie gevallen, en het derde is het punt: "onbereikbaar" is GEEN goedkeuring.
18 * Een ward wiens guardian-server plat ligt heeft een ONGECONTROLEERD vangnet,
19 * en dat is iets anders dan een gecontroleerd vangnet. Niets tonen zou het
20 * eerste als het tweede laten lezen. Zelfde drieslag als in Shaer.
21 */
22const CHECK_NOTE = {
23 malformed: { text: 'Deze guardian heeft zelf guardians. Een noodoproep komt hier niet aan.', cls: 'alarm' },
24 unreachable: { text: 'Deze guardian was niet te bereiken, dus niet gecontroleerd.', cls: 'unknown' },
25 sound: null,
26};
27
28/** Beschikbaarheid in de taal die iedereen zonder handleiding leest. */
29const AVAILABILITY = {
30 active: { label: 'Beschikbaar', cls: 'ok' },
31 away: { label: 'Even weg', cls: 'away' },
32 dormant: { label: 'Offline', cls: 'off' },
33};
34
35function el(tag, cls, text) {
36 const n = document.createElement(tag);
37 if (cls) n.className = cls;
38 if (text != null) n.textContent = text;
39 return n;
40}
41
42function renderGuardians(checks) {
43 const list = $('guardian-list');
44 list.replaceChildren();
45 $('guardians-empty').hidden = state.guardians.length > 0;
46
47 state.guardians.forEach((g) => {
48 const li = el('li', 'card');
49 const head = el('div', 'row');
50 head.append(el('span', 'name', g.handle));
51
52 const av = AVAILABILITY[g.availability] || AVAILABILITY.active;
53 head.append(el('span', `chip ${av.cls}`, av.label));
54 li.append(head);
55
56 // De controle staat WAAR de guardian genoemd wordt, niet in een logboek:
57 // daar heeft het gat betekenis.
58 const note = CHECK_NOTE[checks?.[g.uri]];
59 if (note) li.append(el('p', `note ${note.cls}`, note.text));
60 list.append(li);
61 });
62}
63
64function renderOffers() {
65 const sec = $('offers');
66 const list = $('offer-list');
67 list.replaceChildren();
68 sec.hidden = !state.offers.length;
69
70 state.offers.forEach((o) => {
71 const li = el('li', 'card');
72 const who = o['shaer:candidate'] || o.actor || '';
73 li.append(el('p', 'name', `${who} wil jouw guardian worden`));
74 if (o['shaer:acceptedBy']) {
75 li.append(el('p', 'hint', `${o['shaer:acceptedBy'].length} van de partijen zei al ja`));
76 }
77 list.append(li);
78 });
79}
80
81function renderWaiting() {
82 const sec = $('waiting');
83 const list = $('waiting-list');
84 list.replaceChildren();
85 sec.hidden = !state.waitingFollows.length;
86
87 state.waitingFollows.forEach((w) => {
88 const li = el('li', 'card');
89 li.append(el('p', 'name', w.handle));
90 // Niet "pending": de bal ligt bij iemand die je kent, en geweigerd is
91 // helemaal geen wachten meer. Zelfde drie zinnen als in Shaer.
92 const denied = w.status === 'denied';
93 li.append(el('p', denied ? 'note alarm' : 'hint',
94 denied ? 'Je guardians hebben hier nee tegen gezegd.'
95 : 'Je guardians moeten hier nog ja tegen zeggen.'));
96 list.append(li);
97 });
98}
99
100/** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. */
101async function loadChecks() {
102 if (!state.guardians.length) return;
103 try {
104 const r = await fetch('/ward/api/guardian-checks', { headers: { Accept: 'application/json' } });
105 if (!r.ok) return;
106 const { checks } = await r.json();
107 renderGuardians(checks);
108 } catch { /* stilte is hier juist: dan blijft de lijst zonder oordeel staan */ }
109}
110
111function wireHelp() {
112 const btn = $('help-btn');
113 const hint = $('help-hint');
114 if (!state.guardians.length) {
115 btn.disabled = true;
116 hint.textContent = 'Je hebt nog geen guardians om te vragen.';
117 return;
118 }
119 hint.textContent = `Gaat naar ${state.guardians.length} guardian(s).`;
120}
121
122renderGuardians(null);
123renderOffers();
124renderWaiting();
125wireHelp();
126loadChecks();
Note: See TracBrowser for help on using the repository browser.