Voeg 28 herkenbare kleurplaten en creatieve niveaus toe (v0.4.66-beta) #4
10 changed files with 138 additions and 30 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.4.44-beta
|
0.4.45-beta
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
||||||
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||||
const VERSION = "0.4.44-beta";
|
const VERSION = "0.4.45-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,42 @@ const LANGUAGE_NL_PROGRESSION_WORDS=(()=>{
|
||||||
return out
|
return out
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/* Instapwoorden voor beginnende geletterdheid. In groep 1 zijn woorden alleen
|
||||||
|
beeldankers om een letter en beginklank te herkennen; kinderen hoeven deze
|
||||||
|
woorden nog niet te lezen. Groep 2 voegt korte, vertrouwde woorden toe voor
|
||||||
|
letter-klankkoppeling en het aanvullen van een ontbrekende klank. */
|
||||||
|
const LANGUAGE_EARLY_WORDS=Object.freeze({
|
||||||
|
1:new Set("aap arm bal bed been boek boom boot bos bus deur ei hand huis jas kaas kat kip koe maan mier mond muis neus oog oor pen peer pet pop rok roos sok tas uil vis voet vuur wiel zon".split(" ")),
|
||||||
|
2:new Set("appel beer bij eend geit haas hond kikker paard vos slak spin varken melk taart brood soep rijst mais druif snoep sap koek wortel ster bloem blad wolk regen water steen zand zee gras wind auto fiets trein taxi tram weg zeil step stoel klok bril blok sleutel lamp beker verf schaar school haar hoofd schoen jurk shirt want broek tand lezen eten slapen lopen lachen huilen zingen dansen spelen zitten rennen kijken blij boos bang moe ziek trots rustig baby kind jongen meisje vader moeder zus broer oma opa buur man voetbal puzzel spel knikker winkel boer park klas kok stad dorp dag week jaar feest kaars vlag uur".split(" "))
|
||||||
|
});
|
||||||
|
const LANGUAGE_LOANWORDS_FROM_YEAR5=new Set("wc ski toast jet rail brie pizza citroen cadeau team club cake score hockey bowlen shirt flat".split(" "));
|
||||||
|
|
||||||
|
function languageDataComplexity(word){
|
||||||
|
const w=String(word||"").toLocaleLowerCase("nl-NL").replace(/[^a-z]/g,"");
|
||||||
|
const clusters=(w.match(/[bcdfghjklmnpqrstvwxz]{2,}/g)||[]).reduce((n,x)=>n+x.length-1,0);
|
||||||
|
const patterns=(w.match(/aai|ooi|oei|eeuw|ieuw|sch|ng|nk|ch|aa|ee|oo|uu|oe|ie|ei|ij|ou|au|ui|eu/g)||[]).length;
|
||||||
|
const syllables=(w.match(/[aeiouy]+/g)||[]).length;
|
||||||
|
return w.length+clusters*2+patterns+Math.max(0,syllables-1)*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Een woord stroomt pas in als zowel de geplande woordfamilie, de spelling
|
||||||
|
als de inhoud bij het leerjaar passen. Dit voorkomt bijvoorbeeld dat korte
|
||||||
|
maar abstracte woorden als 'som', 'wet' en 'team' in de kleuterpool komen. */
|
||||||
|
function languageRecommendedYear(item){
|
||||||
|
const word=String(item.nl||"").toLocaleLowerCase("nl-NL");
|
||||||
|
if(LANGUAGE_EARLY_WORDS[1].has(word))return 1;
|
||||||
|
if(LANGUAGE_EARLY_WORDS[2].has(word))return 2;
|
||||||
|
const complexity=languageDataComplexity(word);
|
||||||
|
const syllables=(word.match(/[aeiouy]+/g)||[]).length;
|
||||||
|
const patternYear=/eeuw|ieuw|aai|ooi|oei/.test(word)?5:/sch|ng|nk|ch|oe|ie|ei|ij|ou|au|ui|eu/.test(word)?4:3;
|
||||||
|
const loanwordYear=LANGUAGE_LOANWORDS_FROM_YEAR5.has(word)?5:3;
|
||||||
|
const spellingYear=Math.max(complexity<=9?3:complexity<=14?4:complexity<=19?5:complexity<=24?6:complexity<=30?7:8,syllables>=3?5:syllables>=2?4:3,patternYear,loanwordYear);
|
||||||
|
const themeYear={learning:4,science:4,society:5,arts:3,feelings:3}[item.theme]||3;
|
||||||
|
return Math.max(3,Number(item.yearGroup)||3,spellingYear,themeYear);
|
||||||
|
}
|
||||||
|
const LANGUAGE_NL_BUILTIN_WORDS=LANGUAGE_CONCEPTS.concat(LANGUAGE_NL_PROGRESSION_WORDS)
|
||||||
|
.map(item=>Object.assign({},item,{yearGroup:languageRecommendedYear(item)}));
|
||||||
|
|
||||||
/* De vertrouwde startkaart blijft exact dezelfde 30 woorden houden. */
|
/* De vertrouwde startkaart blijft exact dezelfde 30 woorden houden. */
|
||||||
const START_WORD_KEYS = {
|
const START_WORD_KEYS = {
|
||||||
nl:["monkey","fish","sun","cat","ball","egg","tree","house","moon","boat","hen","mouse","book","star","rose","fire","bike","cake","coat","glasses","clock","pear","cheese","duck","hand","foot","nose","eye","ear","mouth"],
|
nl:["monkey","fish","sun","cat","ball","egg","tree","house","moon","boat","hen","mouse","book","star","rose","fire","bike","cake","coat","glasses","clock","pear","cheese","duck","hand","foot","nose","eye","ear","mouth"],
|
||||||
|
|
@ -537,12 +573,12 @@ const START_WORD_KEYS = {
|
||||||
const WORDS = Object.fromEntries(["nl","en"].map(lang=>[
|
const WORDS = Object.fromEntries(["nl","en"].map(lang=>[
|
||||||
lang, START_WORD_KEYS[lang].map(id=>{
|
lang, START_WORD_KEYS[lang].map(id=>{
|
||||||
const concept=LANGUAGE_CONCEPTS.find(item=>item.id===id);
|
const concept=LANGUAGE_CONCEPTS.find(item=>item.id===id);
|
||||||
return [concept[lang],concept.pic];
|
return [concept[lang],concept.pic,lang==="nl"?languageRecommendedYear(concept):null];
|
||||||
})
|
})
|
||||||
]));
|
]));
|
||||||
|
|
||||||
function languageBuiltinWords(lang=LANG){
|
function languageBuiltinWords(lang=LANG){
|
||||||
return lang==="nl"?LANGUAGE_CONCEPTS.concat(LANGUAGE_NL_PROGRESSION_WORDS):LANGUAGE_CONCEPTS
|
return lang==="nl"?LANGUAGE_NL_BUILTIN_WORDS:LANGUAGE_CONCEPTS
|
||||||
}
|
}
|
||||||
function languageThemeList(lang=LANG){
|
function languageThemeList(lang=LANG){
|
||||||
return LANGUAGE_THEME_DEFS.map(theme=>({
|
return LANGUAGE_THEME_DEFS.map(theme=>({
|
||||||
|
|
@ -663,7 +699,7 @@ function wordSourceList(card){
|
||||||
if(card.startsWith("a")) return anchorSources()[+card.slice(1)].words;
|
if(card.startsWith("a")) return anchorSources()[+card.slice(1)].words;
|
||||||
return generalList();
|
return generalList();
|
||||||
}
|
}
|
||||||
function fillWordSourceSel(sel, card){
|
function fillWordSourceSel(sel, card, yearGroup=null, level=2){
|
||||||
sel.innerHTML = "";
|
sel.innerHTML = "";
|
||||||
const og = document.createElement("option");
|
const og = document.createElement("option");
|
||||||
og.value = "g"; og.textContent = "⭐ "+T("cardStart");
|
og.value = "g"; og.textContent = "⭐ "+T("cardStart");
|
||||||
|
|
@ -671,9 +707,11 @@ function fillWordSourceSel(sel, card){
|
||||||
const builtinGroup = document.createElement("optgroup");
|
const builtinGroup = document.createElement("optgroup");
|
||||||
builtinGroup.label = "📚 "+T("wordThemes");
|
builtinGroup.label = "📚 "+T("wordThemes");
|
||||||
languageThemeList().forEach(theme=>{
|
languageThemeList().forEach(theme=>{
|
||||||
|
const available=yearGroup&&typeof languageWordsForYear==="function"?languageWordsForYear(theme.words,yearGroup,level):theme.words;
|
||||||
|
if(!available.length)return;
|
||||||
const option=document.createElement("option");
|
const option=document.createElement("option");
|
||||||
option.value="b:"+theme.id;
|
option.value="b:"+theme.id;
|
||||||
option.textContent=`${theme.icon} ${theme.name} (${theme.words.length})`;
|
option.textContent=`${theme.icon} ${theme.name} (${available.length})`;
|
||||||
builtinGroup.appendChild(option);
|
builtinGroup.appendChild(option);
|
||||||
});
|
});
|
||||||
sel.appendChild(builtinGroup);
|
sel.appendChild(builtinGroup);
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,9 @@ const LEARNING_YEAR_PROFILES=Object.freeze({
|
||||||
});
|
});
|
||||||
const LANGUAGE_WIDGET_TASKS=Object.freeze({
|
const LANGUAGE_WIDGET_TASKS=Object.freeze({
|
||||||
letters:["","letters en beginklanken","letters en ontbrekende klanken","klankzuivere woorden opbouwen","clusters en tweetekenklanken","meerlettergrepige woorden","regelwoorden en langere woorden","complexe spelling en werkwoorden","spellingkennis zelfstandig toepassen"],
|
letters:["","letters en beginklanken","letters en ontbrekende klanken","klankzuivere woorden opbouwen","clusters en tweetekenklanken","meerlettergrepige woorden","regelwoorden en langere woorden","complexe spelling en werkwoorden","spellingkennis zelfstandig toepassen"],
|
||||||
flits:["","korte bekende woorden","korte klankzuivere woorden","woorden vlot herkennen","langere woorden automatiseren","complexere woorden vlot lezen","schooltaalwoorden automatiseren","laagfrequente schooltaalwoorden","complexe woorden nauwkeurig lezen"],
|
flits:["","letters herkennen en benoemen","korte klankzuivere woorden","woorden vlot herkennen","langere woorden automatiseren","complexere woorden vlot lezen","schooltaalwoorden automatiseren","laagfrequente schooltaalwoorden","complexe woorden nauwkeurig lezen"],
|
||||||
hak:["","woorden in klanken horen","klanken hakken en plakken","klankgroepen samenvoegen","spellingpatronen herkennen","lettergrepen onderzoeken","complexe klankgroepen","woordopbouw en morfemen","woordstructuur verklaren"],
|
hak:["","beginklanken horen en koppelen","klanken hakken en plakken","klankgroepen samenvoegen","spellingpatronen herkennen","lettergrepen onderzoeken","complexe klankgroepen","woordopbouw en morfemen","woordstructuur verklaren"],
|
||||||
ball:["","korte beeldwoorden","korte woorden raden","woorden actief herkennen","langere woorden spellen","complexere spelling","woordenschat consolideren","complexe woordbeelden","gevorderde spelling"],
|
ball:["","letters bij beeldankers kiezen","korte woorden raden","woorden actief herkennen","langere woorden spellen","complexere spelling","woordenschat consolideren","complexe woordbeelden","gevorderde spelling"],
|
||||||
sentence:["","eenvoudige zinnen vertellen","wie-doet-wat-zinnen","enkelvoudige zinnen","zinnen uitbreiden","informatieve zinnen","zinsverband en leestekens","argumenterende zinnen","gevarieerde teksten formuleren"],
|
sentence:["","eenvoudige zinnen vertellen","wie-doet-wat-zinnen","enkelvoudige zinnen","zinnen uitbreiden","informatieve zinnen","zinsverband en leestekens","argumenterende zinnen","gevarieerde teksten formuleren"],
|
||||||
wordtypes:["","woorden benoemen","naamwoorden en doe-woorden","naamwoord en werkwoord","drie woordsoorten","woordsoorten in context","woordsoorten functioneel benoemen","woordsoort en zinsfunctie","taalstructuur analyseren"]
|
wordtypes:["","woorden benoemen","naamwoorden en doe-woorden","naamwoord en werkwoord","drie woordsoorten","woordsoorten in context","woordsoorten functioneel benoemen","woordsoort en zinsfunctie","taalstructuur analyseren"]
|
||||||
});
|
});
|
||||||
|
|
@ -77,15 +77,28 @@ function languageWordsForYear(words,yearGroup,level){
|
||||||
if(!Array.isArray(words)||!words.length)return[];
|
if(!Array.isArray(words)||!words.length)return[];
|
||||||
const year=learningYearGroup(yearGroup);if(!year)return words;
|
const year=learningYearGroup(yearGroup);if(!year)return words;
|
||||||
const introduced=words.filter(item=>!Array.isArray(item)||item[2]==null||Number(item[2])<=year);
|
const introduced=words.filter(item=>!Array.isArray(item)||item[2]==null||Number(item[2])<=year);
|
||||||
|
const hasExplicitYears=words.some(item=>Array.isArray(item)&&item[2]!=null);
|
||||||
|
if(!introduced.length&&hasExplicitYears)return[];
|
||||||
const available=introduced.length?introduced:words;
|
const available=introduced.length?introduced:words;
|
||||||
const caps=[0,5,7,9,12,15,18,22,99],cap=caps[year]+(learningLevel(level)-2)*2;
|
const caps=[0,5,7,9,12,15,18,22,99],cap=caps[year]+(learningLevel(level)-2)*2;
|
||||||
const filtered=available.filter(item=>languageWordComplexity(Array.isArray(item)?item[0]:item)<=cap);
|
const filtered=available.filter(item=>languageWordComplexity(Array.isArray(item)?item[0]:item)<=cap);
|
||||||
if(filtered.length>=Math.min(4,available.length))return filtered;
|
if(filtered.length>=Math.min(4,available.length))return filtered;
|
||||||
return [...available].sort((a,b)=>languageWordComplexity(a[0])-languageWordComplexity(b[0])).slice(0,Math.min(4,available.length))
|
return [...available].sort((a,b)=>languageWordComplexity(a[0])-languageWordComplexity(b[0])).slice(0,Math.min(4,available.length))
|
||||||
}
|
}
|
||||||
|
function languageWidgetWords(words,yearGroup,level,widgetId){
|
||||||
|
const selected=languageWordsForYear(words,yearGroup,level),year=learningYearGroup(yearGroup);
|
||||||
|
if(year!==1||!["flits","hak","ball"].includes(widgetId))return selected;
|
||||||
|
const initials=new Map();
|
||||||
|
selected.forEach(item=>{
|
||||||
|
const word=String(Array.isArray(item)?item[0]:item||"").toLocaleLowerCase("nl-NL");
|
||||||
|
const letter=(word.match(/[a-z]/)||[])[0];
|
||||||
|
if(letter&&!initials.has(letter))initials.set(letter,[letter,Array.isArray(item)?item[1]:"",1,word]);
|
||||||
|
});
|
||||||
|
return [...initials.values()]
|
||||||
|
}
|
||||||
function languageTaskRules(yearGroup,level){
|
function languageTaskRules(yearGroup,level){
|
||||||
const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level);
|
const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level);
|
||||||
return{letterLevel:year<=2?Math.min(2,l):year===3?Math.max(2,l):3,flashSeconds:year<=2?3:year<=4?2:year<=6?1:.5,lives:year<=3?7:year<=5?6:5,wordtypeCount:year<=3?2:year===4?3:4}
|
return{literacyMode:year===1?"letters":year===2?"sounds":"words",letterLevel:year<=2?Math.min(2,l):year===3?Math.max(2,l):3,flashSeconds:year<=2?3:year<=4?2:year<=6?1:.5,lives:year<=3?7:year<=5?6:5,wordtypeCount:year<=3?2:year===4?3:4}
|
||||||
}
|
}
|
||||||
function mathTaskRules(yearGroup,level){
|
function mathTaskRules(yearGroup,level){
|
||||||
const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level),factor=l===1?.25:l===2?.6:1;
|
const year=learningYearGroup(yearGroup);if(!year)return null;const l=learningLevel(level),factor=l===1?.25:l===2?.6:1;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ function mountBall(root, initState, opts){
|
||||||
<div class="mt-score bl-score"></div>
|
<div class="mt-score bl-score"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-card">
|
<div class="mt-card">
|
||||||
|
<div class="fl-pic bl-pic"></div>
|
||||||
<div class="bl-balloons"></div>
|
<div class="bl-balloons"></div>
|
||||||
<div class="bl-word"></div>
|
<div class="bl-word"></div>
|
||||||
<div class="bl-msg"></div>
|
<div class="bl-msg"></div>
|
||||||
|
|
@ -25,13 +26,16 @@ function mountBall(root, initState, opts){
|
||||||
<div class="lg-tray bl-kbd lg-kbd" style="display:flex;"></div>
|
<div class="lg-tray bl-kbd lg-kbd" style="display:flex;"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
const el = s => root.querySelector(s);
|
const el = s => root.querySelector(s);
|
||||||
const srcSel = el(".bl-src"), balloonsEl = el(".bl-balloons"),
|
const srcSel = el(".bl-src"), balloonsEl = el(".bl-balloons"), picEl=el(".bl-pic"),
|
||||||
wordEl = el(".bl-word"), msgEl = el(".bl-msg"),
|
wordEl = el(".bl-word"), msgEl = el(".bl-msg"),
|
||||||
nextBtn = el(".bl-next"), kbdEl = el(".bl-kbd"),
|
nextBtn = el(".bl-next"), kbdEl = el(".bl-kbd"),
|
||||||
scoreEl = el(".bl-score"), sndBtn = el(".lg-snd");
|
scoreEl = el(".bl-score"), sndBtn = el(".lg-snd");
|
||||||
|
|
||||||
function list(){ S.card = normWordSource(S.card); return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); }
|
function list(){
|
||||||
function buildSrc(){ fillWordSourceSel(srcSel, S.card); }
|
S.card = normWordSource(S.card);const selected=languageWidgetWords(wordSourceList(S.card),S.yearGroup,S.level,"ball");
|
||||||
|
if(selected.length)return selected;S.card="g";return languageWidgetWords(wordSourceList("g"),S.yearGroup,S.level,"ball");
|
||||||
|
}
|
||||||
|
function buildSrc(){ fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level);S.card=srcSel.value||"g"; }
|
||||||
"abcdefghijklmnopqrstuvwxyz".split("").forEach(l=>{
|
"abcdefghijklmnopqrstuvwxyz".split("").forEach(l=>{
|
||||||
const b = document.createElement("button");
|
const b = document.createElement("button");
|
||||||
b.textContent = l; b.dataset.l = l;
|
b.textContent = l; b.dataset.l = l;
|
||||||
|
|
@ -55,6 +59,7 @@ function mountBall(root, initState, opts){
|
||||||
msgEl.textContent = ""; msgEl.className = "bl-msg";
|
msgEl.textContent = ""; msgEl.className = "bl-msg";
|
||||||
nextBtn.classList.remove("show");
|
nextBtn.classList.remove("show");
|
||||||
kbdEl.querySelectorAll("button").forEach(b=>{ b.disabled = false; b.style.opacity = 1; });
|
kbdEl.querySelectorAll("button").forEach(b=>{ b.disabled = false; b.style.opacity = 1; });
|
||||||
|
const current=list()[S.order[S.idx]];setPic(picEl,current[1]);picEl.title=current[3]||current[0];
|
||||||
drawWord(false); drawBalloons();
|
drawWord(false); drawBalloons();
|
||||||
scoreEl.textContent = S.stars ? S.stars+" ★" : "";
|
scoreEl.textContent = S.stars ? S.stars+" ★" : "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,13 @@ function mountFlits(root, initState){
|
||||||
const srcSel = el(".fl-src"), durSel = el(".fl-dur"),
|
const srcSel = el(".fl-src"), durSel = el(".fl-dur"),
|
||||||
wordEl = el(".fl-word"), picEl = el(".fl-pic"), cntEl = el(".fl-cnt"), reflect=el(".fl-reflect");
|
wordEl = el(".fl-word"), picEl = el(".fl-pic"), cntEl = el(".fl-cnt"), reflect=el(".fl-reflect");
|
||||||
|
|
||||||
function list(){ S.card = normWordSource(S.card); return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); }
|
function list(){
|
||||||
|
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(){
|
function buildSrc(){
|
||||||
fillWordSourceSel(srcSel, S.card);
|
fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level);
|
||||||
|
S.card=srcSel.value||"g";
|
||||||
durSel.innerHTML = "";
|
durSel.innerHTML = "";
|
||||||
[0.5,1,2,3,5].forEach(d=>{
|
[0.5,1,2,3,5].forEach(d=>{
|
||||||
const o = document.createElement("option");
|
const o = document.createElement("option");
|
||||||
|
|
@ -97,8 +101,9 @@ function mountFlits(root, initState){
|
||||||
el(".fl-flash").textContent = T("flFlash");
|
el(".fl-flash").textContent = T("flFlash");
|
||||||
el(".fl-show").textContent = T("flShow");
|
el(".fl-show").textContent = T("flShow");
|
||||||
el(".fl-next").textContent = T("flNext");
|
el(".fl-next").textContent = T("flNext");
|
||||||
el(".fl-mission").textContent=T("flMission");
|
const letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters";
|
||||||
el(".fl-question").textContent=T("flQuestion");
|
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-got").textContent=T("flGotIt");
|
||||||
el(".fl-again").textContent=T("flAgain");
|
el(".fl-again").textContent=T("flAgain");
|
||||||
buildSrc();
|
buildSrc();
|
||||||
|
|
|
||||||
|
|
@ -45,14 +45,20 @@ function mountHak(root, initState){
|
||||||
const srcSel = el(".hk-src"), wordEl = el(".hk-word"),
|
const srcSel = el(".hk-src"), wordEl = el(".hk-word"),
|
||||||
picEl = el(".hk-pic"), cntEl = el(".hk-cnt");
|
picEl = el(".hk-pic"), cntEl = el(".hk-cnt");
|
||||||
|
|
||||||
function list(){ S.card = normWordSource(S.card); return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level); }
|
function list(){
|
||||||
function buildSrc(){ fillWordSourceSel(srcSel, S.card); }
|
S.card = normWordSource(S.card);const selected=languageWidgetWords(wordSourceList(S.card),S.yearGroup,S.level,"hak");
|
||||||
|
if(selected.length)return selected;S.card="g";return languageWidgetWords(wordSourceList("g"),S.yearGroup,S.level,"hak");
|
||||||
|
}
|
||||||
|
function buildSrc(){ fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level);S.card=srcSel.value||"g"; }
|
||||||
function cur(){ return list()[S.order[S.idx]]; }
|
function cur(){ return list()[S.order[S.idx]]; }
|
||||||
function newRound(){ S.order = shuffle(list().map((_,i)=>i)); S.idx = 0; }
|
function newRound(){ S.order = shuffle(list().map((_,i)=>i)); S.idx = 0; }
|
||||||
function display(){
|
function display(){
|
||||||
const [w, pic] = cur();
|
const [w, pic,, anchor] = cur();
|
||||||
|
const letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters";
|
||||||
wordEl.innerHTML = "";
|
wordEl.innerHTML = "";
|
||||||
if(S.chopped){
|
if(letters&&!S.chopped){
|
||||||
|
const s=document.createElement("span");s.className="hk-whole";s.textContent="?";wordEl.appendChild(s);
|
||||||
|
}else if(S.chopped){
|
||||||
soundChunks(w).forEach((l,i)=>{
|
soundChunks(w).forEach((l,i)=>{
|
||||||
if(i>0){
|
if(i>0){
|
||||||
const sep = document.createElement("span");
|
const sep = document.createElement("span");
|
||||||
|
|
@ -72,6 +78,7 @@ function mountHak(root, initState){
|
||||||
wordEl.appendChild(s);
|
wordEl.appendChild(s);
|
||||||
}
|
}
|
||||||
setPic(picEl, pic);
|
setPic(picEl, pic);
|
||||||
|
picEl.title = anchor||w;
|
||||||
cntEl.textContent = `${T("word")} ${S.idx+1}/${S.order.length}`;
|
cntEl.textContent = `${T("word")} ${S.idx+1}/${S.order.length}`;
|
||||||
}
|
}
|
||||||
function speak(text, rate){
|
function speak(text, rate){
|
||||||
|
|
@ -83,8 +90,10 @@ function mountHak(root, initState){
|
||||||
speechSynthesis.speak(u);
|
speechSynthesis.speak(u);
|
||||||
}
|
}
|
||||||
el(".hk-say").addEventListener("click", ()=>{
|
el(".hk-say").addEventListener("click", ()=>{
|
||||||
if(S.chopped) speak(soundChunks(cur()[0]).join(", "), 0.7);
|
const [word,,,anchor]=cur(),letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters";
|
||||||
else speak(cur()[0], 0.85);
|
if(letters)speak(anchor||word,0.75);
|
||||||
|
else if(S.chopped)speak(soundChunks(word).join(", "),0.7);
|
||||||
|
else speak(word,0.85);
|
||||||
});
|
});
|
||||||
el(".hk-chop").addEventListener("click", ()=>{ S.chopped = true; display(); play(sndGood); });
|
el(".hk-chop").addEventListener("click", ()=>{ S.chopped = true; display(); play(sndGood); });
|
||||||
el(".hk-glue").addEventListener("click", ()=>{ S.chopped = false; display(); play(sndGood); });
|
el(".hk-glue").addEventListener("click", ()=>{ S.chopped = false; display(); play(sndGood); });
|
||||||
|
|
@ -98,10 +107,11 @@ function mountHak(root, initState){
|
||||||
newRound(); S.chopped = false; display();
|
newRound(); S.chopped = false; display();
|
||||||
});
|
});
|
||||||
function labels(){
|
function labels(){
|
||||||
|
const letters=languageTaskRules(S.yearGroup,S.level)?.literacyMode==="letters";
|
||||||
el(".hk-say").textContent = T("hakSay");
|
el(".hk-say").textContent = T("hakSay");
|
||||||
el(".hk-chop").textContent = T("hakChop");
|
el(".hk-chop").textContent = letters?(LANG==="nl"?"Toon letter":"Show letter"):T("hakChop");
|
||||||
el(".hk-glue").textContent = T("hakGlue");
|
el(".hk-glue").textContent = letters?(LANG==="nl"?"Verberg letter":"Hide letter"):T("hakGlue");
|
||||||
el(".hk-hint").textContent = T("hakHint");
|
el(".hk-hint").textContent = letters?(LANG==="nl"?"Luister naar het woord. Welke letter hoor je vooraan?":"Listen to the word. Which letter do you hear first?"):T("hakHint");
|
||||||
buildSrc();
|
buildSrc();
|
||||||
}
|
}
|
||||||
document.addEventListener("langchange", ()=>{ S.card=0; labels(); newRound(); S.chopped=false; display(); });
|
document.addEventListener("langchange", ()=>{ S.card=0; labels(); newRound(); S.chopped=false; display(); });
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,8 @@ function mountLetterGame(root, initState, opts){
|
||||||
cardsEl.innerHTML = "";
|
cardsEl.innerHTML = "";
|
||||||
srcSel = document.createElement("select");
|
srcSel = document.createElement("select");
|
||||||
srcSel.className = "fl-sel";
|
srcSel.className = "fl-sel";
|
||||||
fillWordSourceSel(srcSel, S.card);
|
fillWordSourceSel(srcSel, S.card,S.yearGroup,S.level);
|
||||||
|
S.card=srcSel.value||"g";
|
||||||
srcSel.addEventListener("change", ()=>{ S.card = srcSel.value; newRound(); render(); });
|
srcSel.addEventListener("change", ()=>{ S.card = srcSel.value; newRound(); render(); });
|
||||||
cardsEl.appendChild(srcSel);
|
cardsEl.appendChild(srcSel);
|
||||||
if(!readonly){
|
if(!readonly){
|
||||||
|
|
@ -185,7 +186,9 @@ function mountLetterGame(root, initState, opts){
|
||||||
|
|
||||||
function fullList(){
|
function fullList(){
|
||||||
S.card = normWordSource(S.card);
|
S.card = normWordSource(S.card);
|
||||||
return languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level);
|
const selected=languageWordsForYear(wordSourceList(S.card),S.yearGroup,S.level);
|
||||||
|
if(selected.length)return selected;
|
||||||
|
S.card="g";return languageWordsForYear(wordSourceList("g"),S.yearGroup,S.level);
|
||||||
}
|
}
|
||||||
function newRound(){
|
function newRound(){
|
||||||
const list = fullList();
|
const list = fullList();
|
||||||
|
|
@ -245,13 +248,20 @@ function mountLetterGame(root, initState, opts){
|
||||||
if(allOpt) allOpt.textContent = T("all");
|
if(allOpt) allOpt.textContent = T("all");
|
||||||
|
|
||||||
const [word, pic] = currentWord();
|
const [word, pic] = currentWord();
|
||||||
|
const literacyMode=languageTaskRules(S.yearGroup,S.level)?.literacyMode||"words";
|
||||||
setPic(picEl, pic);
|
setPic(picEl, pic);
|
||||||
countEl.textContent = `${T("word")} ${S.wordIdx+1}/${S.order.length}`;
|
countEl.textContent = `${T("word")} ${S.wordIdx+1}/${S.order.length}`;
|
||||||
starsEl.textContent = S.stars ? S.stars+" ★" : "";
|
starsEl.textContent = S.stars ? S.stars+" ★" : "";
|
||||||
slotsEl.innerHTML = ""; trayEl.innerHTML = "";
|
slotsEl.innerHTML = ""; trayEl.innerHTML = "";
|
||||||
|
|
||||||
const letters = word.split("");
|
const letters = word.split("");
|
||||||
if(S.level===1){
|
if(literacyMode==="letters"){
|
||||||
|
const first=letters[0];
|
||||||
|
instrEl.textContent=LANG==="nl"?"Welke letter hoor je vooraan?":"Which letter do you hear first?";
|
||||||
|
slotsEl.appendChild(makeSlot(first,false));
|
||||||
|
shuffle([first].concat(distractors(word,S.level===1?2:3))).forEach(l=>trayEl.appendChild(makeBlock(l)));
|
||||||
|
}
|
||||||
|
else if(S.level===1){
|
||||||
instrEl.innerHTML = T("instr1");
|
instrEl.innerHTML = T("instr1");
|
||||||
letters.forEach(l=>slotsEl.appendChild(makeSlot(l,true)));
|
letters.forEach(l=>slotsEl.appendChild(makeSlot(l,true)));
|
||||||
shuffle(letters.concat(distractors(word,3))).forEach(l=>trayEl.appendChild(makeBlock(l)));
|
shuffle(letters.concat(distractors(word,3))).forEach(l=>trayEl.appendChild(makeBlock(l)));
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,24 @@ test('taalwidgets delen ruim duizend extra Nederlandse woorden over leerjaar en
|
||||||
assert.equal(data.themesEn.reduce((sum,theme)=>sum+theme.words.length,0),data.concepts.length);
|
assert.equal(data.themesEn.reduce((sum,theme)=>sum+theme.words.length,0),data.concepts.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('woordaanbod volgt beginnende geletterdheid en een expliciete opbouw per groep',async()=>{
|
||||||
|
const data=await languageData();
|
||||||
|
const words=data.themesNl.flatMap(theme=>theme.words);
|
||||||
|
const byYear=year=>words.filter(([, ,introduced])=>introduced<=year).map(([word])=>word);
|
||||||
|
const group1=byYear(1),group2=byYear(2);
|
||||||
|
assert.ok(group1.length>=35&&group1.length<=60);
|
||||||
|
assert.ok(group2.length>=100&&group2.length<=180);
|
||||||
|
for(const word of ['aap','kat','vis','zon','boek','hand'])assert.ok(group1.includes(word),word);
|
||||||
|
for(const word of ['hond','appel','bloem','fiets','lezen','school'])assert.ok(group2.includes(word),word);
|
||||||
|
for(const word of ['som','hoek','team','puber','wet','computer','onderzoek'])assert.ok(!group2.includes(word),word);
|
||||||
|
const introduced=Object.fromEntries(words.map(([word,,year])=>[word,year]));
|
||||||
|
for(const word of ['toast','team','pizza','cadeau'])assert.ok(introduced[word]>=5,word);
|
||||||
|
for(const word of ['aardappel','ziekenhuis'])assert.ok(introduced[word]<=5,word);
|
||||||
|
assert.ok(introduced.pannenkoek<=6,'pannenkoek');
|
||||||
|
for(const [, ,year] of words)assert.ok(year>=1&&year<=8);
|
||||||
|
for(let year=1;year<=8;year++)assert.ok(words.some(([, ,introduced])=>introduced===year),'geen instroom in groep '+year);
|
||||||
|
});
|
||||||
|
|
||||||
test('alle aanvullende woorden hebben een gecontroleerd en correct gekoppeld woordplaatje',async()=>{
|
test('alle aanvullende woorden hebben een gecontroleerd en correct gekoppeld woordplaatje',async()=>{
|
||||||
const data=await languageData();
|
const data=await languageData();
|
||||||
const builtinByName=new Map(data.catalogNl.images.filter(image=>image.builtin).map(image=>[image.name,image]));
|
const builtinByName=new Map(data.catalogNl.images.filter(image=>image.builtin).map(image=>[image.name,image]));
|
||||||
|
|
@ -79,6 +97,8 @@ test('alle oude startwoorden blijven staan en krijgen ook een bibliotheekplaatje
|
||||||
assert.deepEqual(data.words.en.map(([word])=>word),oldEn);
|
assert.deepEqual(data.words.en.map(([word])=>word),oldEn);
|
||||||
const names=new Set(data.catalogNl.images.map(image=>image.name));
|
const names=new Set(data.catalogNl.images.map(image=>image.name));
|
||||||
for(const word of oldNl)assert.ok(names.has(word),word);
|
for(const word of oldNl)assert.ok(names.has(word),word);
|
||||||
|
const startYears=Object.fromEntries(data.words.nl.map(([word,,year])=>[word,year]));
|
||||||
|
assert.equal(startYears.aap,1);assert.equal(startYears.eend,2);assert.equal(startYears.fiets,2);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('vaste en eigen woordplaatjes worden als herbruikbare catalogusitems aangeboden',async()=>{
|
test('vaste en eigen woordplaatjes worden als herbruikbare catalogusitems aangeboden',async()=>{
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ async function progressionContext(){
|
||||||
runInNewContext(pure+`
|
runInNewContext(pure+`
|
||||||
;globalThis.__profiles=LEARNING_YEAR_PROFILES;
|
;globalThis.__profiles=LEARNING_YEAR_PROFILES;
|
||||||
globalThis.__languageWordsForYear=languageWordsForYear;
|
globalThis.__languageWordsForYear=languageWordsForYear;
|
||||||
|
globalThis.__languageWidgetWords=languageWidgetWords;
|
||||||
globalThis.__languageTaskRules=languageTaskRules;
|
globalThis.__languageTaskRules=languageTaskRules;
|
||||||
globalThis.__mathTaskRules=mathTaskRules;
|
globalThis.__mathTaskRules=mathTaskRules;
|
||||||
`,context);
|
`,context);
|
||||||
|
|
@ -63,6 +64,11 @@ test('taalprofiel selecteert eenvoudiger woorden in de onderbouw en bouwt taakst
|
||||||
assert.equal(c.__languageTaskRules(2,2).flashSeconds,3);
|
assert.equal(c.__languageTaskRules(2,2).flashSeconds,3);
|
||||||
assert.equal(c.__languageTaskRules(7,2).flashSeconds,.5);
|
assert.equal(c.__languageTaskRules(7,2).flashSeconds,.5);
|
||||||
assert.ok(c.__languageTaskRules(2,2).lives>c.__languageTaskRules(7,2).lives);
|
assert.ok(c.__languageTaskRules(2,2).lives>c.__languageTaskRules(7,2).lives);
|
||||||
|
assert.equal(c.__languageTaskRules(1,2).literacyMode,'letters');
|
||||||
|
assert.equal(c.__languageTaskRules(2,2).literacyMode,'sounds');
|
||||||
|
assert.equal(c.__languageTaskRules(3,2).literacyMode,'words');
|
||||||
|
const letters=JSON.parse(JSON.stringify(c.__languageWidgetWords(words,1,2,'flits')));
|
||||||
|
assert.ok(letters.every(([letter,,,anchor])=>letter.length===1&&anchor.startsWith(letter)));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('rekenprofiel vergroot getalgebied, tafels en breuken per leerjaar',async()=>{
|
test('rekenprofiel vergroot getalgebied, tafels en breuken per leerjaar',async()=>{
|
||||||
|
|
@ -92,7 +98,8 @@ test('alle taal- en rekenwidgets ontvangen de centrale leerjaarstate en tonen he
|
||||||
assert.match(pupil,/applyLearningLevelToState/);
|
assert.match(pupil,/applyLearningLevelToState/);
|
||||||
assert.match(pupil,/appendLearningProfileBadge/);
|
assert.match(pupil,/appendLearningProfileBadge/);
|
||||||
assert.match(progressions,/managedLearningLevel/);
|
assert.match(progressions,/managedLearningLevel/);
|
||||||
for(const source of [letters,flits,hak,ball])assert.match(source,/languageWordsForYear/);
|
assert.match(letters,/languageWordsForYear/);
|
||||||
|
for(const source of [flits,hak,ball])assert.match(source,/languageWidgetWords/);
|
||||||
assert.match(math,/mathTaskRules/);
|
assert.match(math,/mathTaskRules/);
|
||||||
assert.match(numberline,/mathTaskRules/);
|
assert.match(numberline,/mathTaskRules/);
|
||||||
for(const mount of ['mountFractions','mountClock','mountPlacevalue','mountMoney','mountWordtypes']){
|
for(const mount of ['mountFractions','mountClock','mountPlacevalue','mountMoney','mountWordtypes']){
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue