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
75 lines
6 KiB
JavaScript
75 lines
6 KiB
JavaScript
/* widget: zinsbouwer met aanpasbare woordbanken */
|
||
function mountSentence(root, initState){
|
||
const year=learningYearGroup(initState?.yearGroup);
|
||
const early=year&&year<=3,advanced=year&&year>=7;
|
||
const defaults=LANG==='nl' ? (early ? {
|
||
who:['De hond','Ik','Sam','De juf'],does:['leest','speelt','maakt','ziet'],what:['een boek','buiten','een huis','de bal'],
|
||
} : advanced ? {
|
||
who:['Hoewel de bron','De schrijver','Onze klas','Volgens de onderzoeker'],does:['vergelijkt','beargumenteert','verklaart','beoordeelt'],what:['de informatie kritisch','twee standpunten','de uitkomst zorgvuldig','het bewijs in de tekst'],
|
||
} : {
|
||
who:['De hond','Mijn vriend','De juf','Sara'], does:['lees','speelt','maakt','ziet'],
|
||
what:['een boek','op het plein','een mooie tekening','de rode bal'],
|
||
}) : (early ? {
|
||
who:['The dog','I','Sam','The teacher'],does:['reads','plays','makes','sees'],what:['a book','outside','a house','the ball'],
|
||
} : advanced ? {
|
||
who:['Although the source','The writer','Our class','According to the researcher'],does:['compares','argues','explains','assesses'],what:['the information critically','two viewpoints','the result carefully','the evidence in the text'],
|
||
} : {
|
||
who:['The dog','My friend','The teacher','Sarah'], does:['reads','plays','makes','sees'],
|
||
what:['a book','in the playground','a beautiful drawing','the red ball'],
|
||
});
|
||
const legacyNl={who:['De hond','Mijn vriend','De juf','Sara'],does:['lees','speelt','maakt','ziet'],what:['een boek','op het plein','een mooie tekening','de rode bal']};
|
||
const legacyEn={who:['The dog','My friend','The teacher','Sarah'],does:['reads','plays','makes','sees'],what:['a book','in the playground','a beautiful drawing','the red ball']};
|
||
const savedBanks=initState?.banks,isLegacyDefault=savedBanks&&[legacyNl,legacyEn].some(bank=>JSON.stringify(bank)===JSON.stringify(savedBanks));
|
||
const chosenBanks=initState?.managedLearningLevel&&isLegacyDefault?defaults:(savedBanks||defaults);
|
||
const S={banks:JSON.parse(JSON.stringify(chosenBanks)),sentence:[...(initState?.sentence||[])]};
|
||
root.innerHTML=`
|
||
<div class="sb">
|
||
<div class="sb-sentence" aria-live="polite"></div>
|
||
<div class="sb-banks"></div>
|
||
<details class="edu-settings widget-gear"><summary>⚙ <span class="sb-add-label"></span></summary>
|
||
<div class="sb-add edu-controls">
|
||
<select class="sb-kind"><option value="who"></option><option value="does"></option><option value="what"></option></select>
|
||
<input class="sb-word" type="text" maxlength="40">
|
||
<button class="tbtn ghost sb-add-btn" type="button">+</button>
|
||
</div></details>
|
||
<div class="sb-actions"><button class="tbtn ghost sb-surprise" type="button"></button><button class="tbtn ghost sb-back" type="button">↶</button><button class="tbtn ghost sb-clear" type="button"></button><button class="tbtn sb-speak" type="button"></button></div>
|
||
</div>`;
|
||
const q=s=>root.querySelector(s), sentenceEl=q('.sb-sentence'), banksEl=q('.sb-banks'), kind=q('.sb-kind'), word=q('.sb-word');
|
||
function sentenceText(){ return S.sentence.map(x=>x.word).join(' ').replace(/\s+([,.!?])/g,'$1'); }
|
||
function renderSentence(){
|
||
sentenceEl.innerHTML="";
|
||
if(!S.sentence.length){ sentenceEl.textContent=T('sentenceHint'); sentenceEl.classList.add('empty'); return; }
|
||
sentenceEl.classList.remove('empty');
|
||
S.sentence.forEach(part=>{const span=document.createElement('span');span.className='sb-part '+part.kind;span.textContent=part.word;sentenceEl.appendChild(span);});
|
||
}
|
||
function renderBanks(){
|
||
banksEl.innerHTML="";
|
||
['who','does','what'].forEach(k=>{
|
||
const group=document.createElement('section'); group.className='sb-bank '+k;
|
||
const h=document.createElement('h3'); h.textContent=T('sentence_'+k); group.appendChild(h);
|
||
const chips=document.createElement('div'); chips.className='sb-chips';
|
||
S.banks[k].forEach((w,i)=>{
|
||
const wrap=document.createElement('span'); wrap.className='sb-chip-wrap';
|
||
const chip=document.createElement('button'); chip.type='button'; chip.className='sb-chip'; chip.textContent=w;
|
||
chip.addEventListener('click',()=>{S.sentence.push({kind:k,word:w});renderSentence();});
|
||
const remove=document.createElement('button'); remove.type='button'; remove.className='sb-chip-remove'; remove.textContent='×'; remove.title=T('sentenceRemove');
|
||
remove.addEventListener('click',()=>{S.banks[k].splice(i,1);renderBanks();}); wrap.append(chip,remove); chips.appendChild(wrap);
|
||
});
|
||
group.appendChild(chips); banksEl.appendChild(group);
|
||
});
|
||
}
|
||
function addWord(){const w=word.value.trim();if(!w)return;S.banks[kind.value].push(w);word.value='';renderBanks();}
|
||
q('.sb-add-btn').addEventListener('click',addWord); word.addEventListener('keydown',e=>{e.stopPropagation();if(e.key==='Enter')addWord();});
|
||
q('.sb-back').addEventListener('click',()=>{S.sentence.pop();renderSentence();});
|
||
q('.sb-clear').addEventListener('click',()=>{S.sentence=[];renderSentence();});
|
||
q('.sb-surprise').addEventListener('click',()=>{S.sentence=['who','does','what'].filter(k=>S.banks[k].length).map(kind=>({kind,word:S.banks[kind][Math.floor(Math.random()*S.banks[kind].length)]}));renderSentence();sndGood();confetti(sentenceEl)});
|
||
q('.sb-speak').addEventListener('click',()=>{const text=sentenceText();if(text&&'speechSynthesis'in window){speechSynthesis.cancel();const u=new SpeechSynthesisUtterance(text);u.lang=LANG==='nl'?'nl-NL':'en-GB';speechSynthesis.speak(u);}});
|
||
function labels(){
|
||
q('.sb-add-label').textContent=T('widgetSettings');
|
||
[...kind.options].forEach(o=>o.textContent=T('sentence_'+o.value)); word.placeholder=T('sentenceAdd');
|
||
q('.sb-surprise').textContent=T('sentenceSurprise');q('.sb-clear').textContent=T('sentenceClear'); q('.sb-speak').textContent=T('sentenceSpeak'); renderSentence();renderBanks();
|
||
}
|
||
root.classList.toggle("managed-learning-level",!!initState?.managedLearningLevel);
|
||
document.addEventListener('langchange',labels);labels();
|
||
return {getState:()=>({banks:S.banks,sentence:S.sentence})};
|
||
}
|