/* teach - leerling-omgeving: toont enkel de widgets die de leerkracht heeft klaargezet (via een toewijzing aan de klas of de leerling zelf), in speelmodus (readonly) en zonder de sleep/vergroot/sluit-chrome van het bord. Geen momentopname: /my/assignment zoekt live in het bord van de leerkracht, dus deze pagina pollt periodiek zodat nieuwe woorden vanzelf doorkomen. Modus per toewijzing: "werken" (default, leerling werkt in de widget en voortgang wordt gelogd) of "kijken" (alleen meekijken: alle interactie geblokkeerd via een overlay, snellere poll zodat wijzigingen van de leerkracht vlot doorkomen). */ const PUPIL_WIDGET_CATS = ["taal", "rekenen"]; const PUPIL_POLL_MS = { kijken: 5000, werken: 20000 }; const pupilView = document.getElementById("pupilView"); let pupilPollTimer = null; let pupilLastSig = null; let pupilMode = "werken"; function pupilAllowedDefs(){ return REGISTRY.filter(d => PUPIL_WIDGET_CATS.includes(d.cat)); } async function renderPupilWidgets(widgets, mode){ pupilView.innerHTML = ""; const defs = pupilAllowedDefs(); const watch = mode === "kijken"; const usable = widgets .map(w => ({ w, def: defs.find(d => d.id === w.id) })) .filter(x => x.def); if(!usable.length){ const hint = document.createElement("div"); hint.className = "pupil-empty"; hint.textContent = T("pupilEmpty"); pupilView.appendChild(hint); return; } /* klassenmanagement: groep/niveau per vakgebied ophalen vóór het mounten, zodat een widget zonder eigen opgeslagen niveau met de door de groepsleiding ingestelde waarde start i.p.v. altijd stilzwijgend niveau 1 - één keer per vakgebied dat hier daadwerkelijk voorkomt, niet per widget. */ const subjects = [...new Set(usable.map(x => x.def.cat))]; const levels = {}; await Promise.all(subjects.map(async subject=>{ try{ levels[subject] = await api("/my/level?subject="+subject); } catch(e){ levels[subject] = null; } })); usable.forEach(({ w, def }, idx)=>{ const card = document.createElement("div"); card.className = "widget pupil-card" + (watch ? " watch" : ""); card.dataset.widget = def.id; card.style.width = `min(100%, ${def.w}px)`; card.style.minWidth = "0"; card.style.minHeight = def.h + "px"; const body = document.createElement("div"); body.className = "widget-body"; card.appendChild(body); if(watch){ const badge = document.createElement("div"); badge.className = "pupil-watch-badge"; badge.textContent = "👁 " + T("pupilWatching"); card.appendChild(badge); } pupilView.appendChild(card); /* borden die vóór de invoering van stabiele widget-ids zijn opgeslagen hebben nog geen wid - dan is positie in de lijst de terugval-identiteit, stabiel zolang de leerkracht de widgets op het bord niet herschikt */ const widgetId = w.wid || (def.id + ":" + idx); /* in kijken-modus geen voortgang: de leerling doet zelf niets. In werken-modus moet de leerling juist wél kunnen werken - readonly:true stond hier sinds v0.3.15-beta abusievelijk ook in de werken-tak (een kopieerfout toen kijken/werken werd toegevoegd). De twee widgets die opts.readonly echt lezen (letters.js/anchor.js) gebruiken het alleen om hun eigen bewerk-gereedschap (woordenlijst-editor, opslaan/bibliotheek) te verbergen, niet om de kernoefening te blokkeren - maar dat gereedschap hoort een leerling in werken-modus wél te zien, dus dit was hoe dan ook verkeerd om, los van de vraag of dit "voortgang werkt niet" verklaart. */ const opts = watch ? { readonly:true } : { onProgress: (p)=>{ /* eerder een stille .catch(()=>{}) - een mislukte log (verlopen sessie, geen toewijzing meer, servergolf) verdween daarmee spoorloos, wat een "voortgang werkt niet"-klacht onmogelijk te onderzoeken maakt. Nu zichtbaar in de console (geen storende UI voor de leerling, wél een spoor voor wie het probleem moet natrekken). */ api("/my/progress", { method:"POST", body: { widgetId, widgetType: def.id, ...p } }) .catch(e=>console.warn("voortgang loggen mislukt:", e.message)); }}; /* alleen aanvullen wat het bord zelf nog niet expliciet heeft ingesteld - een leerkracht die zelf al een niveau op dit bord koos, blijft leidend. */ const lvl = levels[def.cat]; const state = w.state ? { ...w.state } : {}; if(lvl){ if(state.level == null) state.level = lvl.level; if(def.cat === "rekenen" && lvl.yearGroup != null) state.yearGroup = lvl.yearGroup; } def.mount(body, Object.keys(state).length ? state : null, opts); }); } async function pupilPoll(){ try{ const res = await api("/my/assignment"); const mode = res.mode === "kijken" ? "kijken" : "werken"; /* modus in de signature zodat een moduswissel ook zonder widget-wijziging een her-render (en dus de juiste overlay) oplevert */ const sig = mode + "|" + JSON.stringify(res.widgets || []); if(sig !== pupilLastSig){ pupilLastSig = sig; await renderPupilWidgets(res.widgets || [], mode); } if(mode !== pupilMode){ pupilMode = mode; clearInterval(pupilPollTimer); pupilPollTimer = setInterval(pupilPoll, PUPIL_POLL_MS[mode]); } }catch(e){ /* stille mislukking - volgende poll probeert opnieuw */ } } function startPupilView(){ document.body.classList.add("pupil-mode"); pupilLastSig = null; pupilMode = "werken"; pupilPoll(); clearInterval(pupilPollTimer); pupilPollTimer = setInterval(pupilPoll, PUPIL_POLL_MS[pupilMode]); } function stopPupilView(){ document.body.classList.remove("pupil-mode"); clearInterval(pupilPollTimer); pupilPollTimer = null; pupilView.innerHTML = ""; }