All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
Bij veel widgets nam de instellingenstrook te veel hoogte in, vooral bij een smal widgetvenster waar de rij naar meerdere regels uitwaaierde. Nieuw gedeeld patroon: besturing die voortdurend nodig is (kleur, niveau, woordbron) blijft in één compacte rij zichtbaar; besturing die maar zelden wordt aangepast (thema, geluid aan/uit, invoerwijze, bereik) gaat achter een inklapbaar ⚙-paneel. Dit patroon bestond al voor klok/geld/rekengetallen/ wereld (learningShell() in learning.js) - nu als widgetGear()/gearField() in core.js beschikbaar voor de rest van de app, met dezelfde .edu-settings- opmaak zodat het overal hetzelfde oogt en werkt. - kleurplaten: twee volle rijen (thema/plaat + kwast/emmer/gum/kleur/dikte/ hulp/reset/klaar) teruggebracht naar één vaste rij (gereedschap, kleur, dikte, klaar) plus het ingeklapte paneel (thema, plaat, kleurhulp, reset). Losstaand: coloring.css gebruikte @media(max-width:620px) in plaats van de @container widget-conventie die de rest van de app gebruikt, waardoor die regels nooit afgingen bij het verkleinen van het widgetvenster zelf - nu gecorrigeerd. - letterblokken: geluid, invoerwijze en aantal kaarten in het paneel; niveau en woordbron blijven vast. - rekenwidgets (optellen/aftrekken/keer/delen/procenten): geluid en invoerwijze in het paneel; niveau blijft vast. - ballonnenwoord: geluid in het paneel. - flitswoorden: flitstijd in het paneel. - getallenlijn: het hele bereik-instelblok (van/tot/stap/nieuw) in het paneel - dat stond toch al uit voor leerlingen met een beheerd niveau. - zinsbouwer: het rijtje voor eigen woorden toevoegen in het paneel; de speelknoppen (verrassing/ongedaan/wissen/voorlezen) blijven vast. - timer: geluid en weergavevorm in het paneel; starttijden en start/reset blijven vast. - geluidsmeter: de twee drempelschuifjes plus de privacynotitie in het paneel. Widgets die dit patroon al hadden (klok, geld, breuken, plaatswaarde, woordsoorten, bomen/vogels/planten, topografie, stemming, stemming-poll, dagplanning) of waar de rij al compact genoeg was (schrijfblad, hakken en plakken, dobbelstenen, notities, naamtrekker) zijn ongewijzigd gelaten. Geverifieerd door elke aangepaste widget live via de echte makeWidget()/ REGISTRY in een headless browser te openen, het paneel open en dicht te klappen en te controleren op consolefouten - ook op de minimale widgetbreedte (coloring, minW 390). 202 tests groen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YErxhic5w9K57zmt8Q1uS
125 lines
6 KiB
JavaScript
125 lines
6 KiB
JavaScript
/* widget: Flitswoorden */
|
|
/* =========================================================
|
|
Flash words widget
|
|
==========================================================*/
|
|
function mountFlits(root, initState, opts){
|
|
const onProgress = opts && opts.onProgress;
|
|
const S = { level:1, yearGroup:null, managedLearningLevel:false, card:0, dur:2, idx:0, order:[], score:0, tries:0, assignmentWords:null, assignmentThemeName:"" };
|
|
if(initState){ S.level=initState.level||1;S.yearGroup=initState.yearGroup||null;S.managedLearningLevel=!!initState.managedLearningLevel;S.card = initState.card ?? 0; S.dur = initState.dur || 2; S.score=+initState.score||0; S.tries=+initState.tries||0;S.assignmentWords=Array.isArray(initState.assignmentWords)?initState.assignmentWords:null;S.assignmentThemeName=typeof initState.assignmentThemeName==="string"?initState.assignmentThemeName:""; }
|
|
const assignedRules=languageTaskRules(S.yearGroup,S.level);if(S.managedLearningLevel&&assignedRules)S.dur=assignedRules.flashSeconds;
|
|
let flashTimer = null, assessed=false;
|
|
|
|
root.innerHTML = `
|
|
<div class="mt">
|
|
<div class="mt-top">
|
|
<select class="fl-sel fl-src"></select>
|
|
<div class="mt-score fl-cnt"></div>
|
|
</div>
|
|
<p class="play-mission fl-mission"></p>
|
|
<div class="mt-card">
|
|
<div class="fl-word dim">• • •</div>
|
|
<div class="fl-pic"></div>
|
|
</div>
|
|
<div class="fl-reflect" hidden>
|
|
<p class="fl-question"></p>
|
|
<div><button class="tbtn fl-got"></button><button class="tbtn ghost fl-again"></button></div>
|
|
</div>
|
|
<div class="trow" style="display:flex;gap:10px;justify-content:center;flex:none;">
|
|
<button class="tbtn fl-flash"></button>
|
|
<button class="tbtn ghost fl-show"></button>
|
|
<button class="tbtn ghost fl-next"></button>
|
|
</div>
|
|
</div>`;
|
|
const el = s => root.querySelector(s);
|
|
const durSel=document.createElement("select");durSel.className="fl-sel fl-dur";
|
|
el(".mt").insertBefore(widgetGear([gearField(T("flDurLabel"),durSel)]),el(".mt-top").nextSibling);
|
|
const srcSel = el(".fl-src"),
|
|
wordEl = el(".fl-word"), picEl = el(".fl-pic"), cntEl = el(".fl-cnt"), reflect=el(".fl-reflect");
|
|
|
|
function list(){
|
|
if(S.assignmentWords?.length)return languageWidgetWords(S.assignmentWords,S.yearGroup,S.level,"flits");
|
|
S.card = normWordSource(S.card);const selected=languageWidgetWords(wordSourceList(S.card),S.yearGroup,S.level,"flits");
|
|
if(selected.length)return selected;S.card="g";return languageWidgetWords(wordSourceList("g"),S.yearGroup,S.level,"flits");
|
|
}
|
|
function buildSrc(){
|
|
if(S.assignmentWords?.length){srcSel.innerHTML="";srcSel.appendChild(new Option("🎯 "+(S.assignmentThemeName||T("castTheme")),"assignment"));srcSel.disabled=true;}
|
|
else{srcSel.disabled=false;fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level);S.card=srcSel.value||"g";}
|
|
durSel.innerHTML = "";
|
|
[0.5,1,2,3,5].forEach(d=>{
|
|
const o = document.createElement("option");
|
|
o.value = d; o.textContent = d+" "+T("flSec");
|
|
durSel.appendChild(o);
|
|
});
|
|
durSel.value = S.dur;
|
|
}
|
|
function newRound(){
|
|
S.order = shuffle(list().map((_,i)=>i));
|
|
S.idx = 0;
|
|
}
|
|
function cur(){ return list()[S.order[S.idx]]; }
|
|
function score(){ cntEl.textContent = `${T("word")} ${S.idx+1}/${S.order.length} · ${T("flScore").replace("{score}",S.score)}`; }
|
|
function hide(ask=false){
|
|
wordEl.textContent = "• • •";
|
|
wordEl.classList.add("dim");
|
|
picEl.textContent = "";
|
|
reflect.hidden=!ask||assessed;
|
|
}
|
|
function flash(){
|
|
clearTimeout(flashTimer);
|
|
assessed=false;
|
|
wordEl.textContent = cur()[0];
|
|
wordEl.classList.remove("dim");
|
|
picEl.textContent = "";
|
|
reflect.hidden=true; score();
|
|
flashTimer = setTimeout(()=>hide(true), S.dur*1000);
|
|
}
|
|
function show(){
|
|
clearTimeout(flashTimer);
|
|
wordEl.textContent = cur()[0];
|
|
wordEl.classList.remove("dim");
|
|
setPic(picEl, cur()[1]);
|
|
reflect.hidden=assessed;
|
|
}
|
|
function assess(ok){
|
|
if(assessed)return;assessed=true;
|
|
S.tries++; if(ok){S.score++;sndGood();confetti(el('.mt-card'));}else sndBad();
|
|
/* de zelfcheck van de leerling ("gelukt" / "nog oefenen") was tot nu toe
|
|
alleen een sessieteller; het is het enige goed/fout-signaal dat deze
|
|
widget heeft en dus precies wat de voortgang bij taal miste */
|
|
if(onProgress) onProgress(languageProgressPayload({
|
|
word: cur()[0], solved: ok, stars: ok?1:0, selfCheck: true }));
|
|
score(); reflect.hidden=true;
|
|
}
|
|
el(".fl-flash").addEventListener("click", flash);
|
|
el(".fl-show").addEventListener("click", show);
|
|
el(".fl-got").addEventListener("click",()=>assess(true));
|
|
el(".fl-again").addEventListener("click",()=>assess(false));
|
|
el(".fl-next").addEventListener("click", ()=>{
|
|
S.idx++;
|
|
if(S.idx >= S.order.length){ celebrateAvatar(root); newRound(); }
|
|
flash();
|
|
});
|
|
srcSel.addEventListener("change", ()=>{
|
|
S.card = srcSel.value;
|
|
newRound(); hide();
|
|
cntEl.textContent = "";
|
|
});
|
|
durSel.addEventListener("change", ()=>{ S.dur = +durSel.value; });
|
|
function labels(){
|
|
el(".fl-flash").textContent = T("flFlash");
|
|
el(".fl-show").textContent = T("flShow");
|
|
el(".fl-next").textContent = T("flNext");
|
|
const letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters";
|
|
el(".fl-mission").textContent=letters?(LANG==="nl"?"Bekijk en benoem de letter.":"Look at and name the letter."):T("flMission");
|
|
el(".fl-question").textContent=letters?(LANG==="nl"?"Herkende je de letter?":"Did you recognise the letter?"):T("flQuestion");
|
|
el(".fl-got").textContent=T("flGotIt");
|
|
el(".fl-again").textContent=T("flAgain");
|
|
buildSrc();
|
|
if(S.order.length)score();
|
|
}
|
|
document.addEventListener("langchange", ()=>{ if(!S.assignmentWords)S.card=0; labels(); newRound(); hide(); });
|
|
document.addEventListener("wordschange", ()=>{ buildSrc(); newRound(); hide(); });
|
|
labels(); newRound();
|
|
root.classList.toggle("managed-learning-level",S.managedLearningLevel);
|
|
return { getState: ()=>({card:S.card, dur:S.dur, score:S.score, tries:S.tries}) };
|
|
}
|