teach/public/js/data.js
Ramon d05b12bb86
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 8s
v0.2.06-beta: automatische plaatjes per woord in Ankerwoorden
- Ankerwoorden-widget: elk woord krijgt nu automatisch een passend plaatje via de ARASAAC-pictogrammendienst, in plaats van steeds hetzelfde ankersymbool
- Plaatjes worden per woord gecachet en bewaard in de bordstatus, zodat ze niet steeds opnieuw opgehaald worden
- Bij geen resultaat of een netwerkfout valt de widget terug op het ankersymbool
- ARASAAC-opzoekfunctie hergebruikt vanuit de bestaande woordkaart-editor (geen dubbele code meer)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 08:55:43 +02:00

140 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* teach - woorddata: algemene kaart, thema's, gedeelde woordbronnen */
/* =========================================================
Letter game data
==========================================================*/
const DOTCODE = {
a:[1], b:[1,2], c:[1,4], d:[1,4,5], e:[1,5], f:[1,2,4], g:[1,2,4,5], h:[1,2,5],
i:[2,4], j:[2,4,5], k:[1,3], l:[1,2,3], m:[1,3,4], n:[1,3,4,5], o:[1,3,5],
p:[1,2,3,4], q:[1,2,3,4,5], r:[1,2,3,5], s:[2,3,4], t:[2,3,4,5], u:[1,3,6],
v:[1,2,3,6], w:[2,4,5,6], x:[1,3,4,6], y:[1,3,4,5,6], z:[1,3,5,6],
/* digits use the braille number patterns (aj) */
"1":[1], "2":[1,2], "3":[1,4], "4":[1,4,5], "5":[1,5],
"6":[1,2,4], "7":[1,2,4,5], "8":[1,2,5], "9":[2,4], "0":[2,4,5]
};
const VOWELS = "aeiou";
function dotGrid(letter){
const code = DOTCODE[letter] || [];
const grid = document.createElement("div");
grid.className = "dots";
[1,4,2,5,3,6].forEach(p=>{
const d = document.createElement("i");
d.className = code.includes(p) ? "on" : "off";
grid.appendChild(d);
});
return grid;
}
/* built-in general words: ONE card per language (extendable via the editor) */
const WORDS = {
nl:[
["aap","🐵"],["vis","🐟"],["zon","☀️"],["kat","🐈"],["bal","⚽"],["ei","🥚"],
["boom","🌳"],["huis","🏠"],["maan","🌙"],["boot","⛵"],["kip","🐔"],["muis","🐭"],
["boek","📕"],["ster","⭐"],["roos","🌹"],["vuur","🔥"],["fiets","🚲"],["taart","🎂"],
["jas","🧥"],["bril","👓"],["klok","⏰"],["peer","🍐"],["kaas","🧀"],["eend","🦆"],
["hand","✋"],["voet","🦶"],["neus","👃"],["oog","👁️"],["oor","👂"],["mond","👄"]
],
en:[
["cat","🐈"],["dog","🐕"],["sun","☀️"],["egg","🥚"],["ball","⚽"],["fish","🐟"],
["tree","🌳"],["moon","🌙"],["boat","⛵"],["star","⭐"],["book","📕"],["cake","🎂"],
["house","🏠"],["mouse","🐭"],["apple","🍎"],["train","🚂"],["fire","🔥"],["rose","🌹"],
["bear","🐻"],["duck","🦆"],["frog","🐸"],["bee","🐝"],["cow","🐄"],["pig","🐷"],
["hand","✋"],["foot","🦶"],["nose","👃"],["eye","👁️"],["ear","👂"],["milk","🥛"]
]
};
/* teacher's own additions to the general card (persisted per user) */
let GENERAL_EXTRA = {nl:[], en:[]};
function generalList(){ return WORDS[LANG].concat(GENERAL_EXTRA[LANG]); }
const shuffle = a => { a=a.slice(); for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]];} return a; };
/* custom themes: folders of own words.
{nl:[{name:"Herfst", words:[[word,pic],...]}], en:[...]} */
let THEMES = {nl:[], en:[]};
function themesFromData(data){
if(!data) return {nl:[],en:[]};
if(data.themes) return data.themes;
/* migrate old flat word lists into one theme */
const t = {nl:[],en:[]};
if(data.words) ["nl","en"].forEach(l=>{
if(Array.isArray(data.words[l]) && data.words[l].length)
t[l] = [{name: l==="nl" ? "Mijn woorden" : "My words", words: data.words[l]}];
});
return t;
}
const isImg = p => typeof p==="string" && (p.startsWith("data:") || p.startsWith("http"));
function setPic(el, pic){
if(isImg(pic)){ el.innerHTML = ""; const im = document.createElement("img"); im.src = pic; im.alt=""; el.appendChild(im); }
else el.textContent = pic;
}
/* ---- ARASAAC pictogram lookup (shared by the word editor + anchor grid) ---- */
async function arasaacSearch(term, lang){
const resp = await fetch(`https://api.arasaac.org/api/pictograms/${lang}/search/${encodeURIComponent(term)}`);
if(!resp.ok) throw new Error("http "+resp.status);
return resp.json();
}
const arasaacUrl = id => `https://static.arasaac.org/pictograms/${id}/${id}_300.png`;
/* ---- shared word source (general card + themes + anchor grids) ---- */
/* anchor-word widgets register their words here so every language widget
can use them as a theme automatically */
let ANCHORS = []; /* [{el, name, words:[[w,pic],...]}] */
function anchorSources(){
ANCHORS = ANCHORS.filter(a=>a.el && document.contains(a.el));
return ANCHORS.filter(a=>a.words.length);
}
function normWordSource(card){
if(typeof card==="string"){
if(card.startsWith("t")){
const th = THEMES[LANG][+card.slice(1)];
if(th && th.words.length) return card;
}else if(card.startsWith("a")){
if(anchorSources()[+card.slice(1)]) return card;
}
}
return "g";
}
function wordSourceList(card){
card = normWordSource(card);
if(card.startsWith("t")) return THEMES[LANG][+card.slice(1)].words;
if(card.startsWith("a")) return anchorSources()[+card.slice(1)].words;
return generalList();
}
function fillWordSourceSel(sel, card){
sel.innerHTML = "";
const og = document.createElement("option");
og.value = "g"; og.textContent = T("cardGeneral");
sel.appendChild(og);
THEMES[LANG].forEach((th,i)=>{
if(!th.words.length) return;
const o = document.createElement("option");
o.value = "t"+i; o.textContent = "★ "+th.name;
sel.appendChild(o);
});
anchorSources().forEach((a,i)=>{
const o = document.createElement("option");
o.value = "a"+i; o.textContent = "⚓ "+a.name;
sel.appendChild(o);
});
sel.value = normWordSource(card);
if(sel.selectedIndex < 0) sel.selectedIndex = 0;
}
/* emoji palette (grouped) */
const splitEmoji = s => {
const out = [];
for(const ch of s){
if(out.length && (ch==="" || ch==="")) out[out.length-1]+=ch;
else if(out.length && out[out.length-1].endsWith("")) out[out.length-1]+=ch;
else out.push(ch);
}
return out;
};
const EMOJI_CATS = [
splitEmoji("🐵🐶🐱🐭🐹🐰🦊🐻🐼🐨🐯🦁🐮🐷🐸🐔🐧🐦🦆🦉🐴🦄🐝🐛🦋🐌🐞🐠🐟🐬🐳🐢🐍🦎🐘🦒"),
splitEmoji("🍎🍐🍊🍋🍌🍉🍇🍓🍒🍑🥝🍅🥕🌽🥔🧀🥚🍞🥐🥞🍕🍟🍦🍪🎂🍫🍬🥛🧃🍯"),
splitEmoji("🌳🌲🌴🌵🌷🌹🌻🌸💐🍄⭐🌙☀️⛅🌈❄️🔥💧🌊🪨"),
splitEmoji("🚗🚕🚌🚑🚒🚜🚲🛴🏍️🚂✈️🚁⛵🚀🛶🚢"),
splitEmoji("⚽🏀🎈🎁🧸📕✏️🖍️✂️🎨🥁🎸👓⌚⏰💡🔑🧤🧢👟👗🧥🛏️🪑🚪🏠"),
splitEmoji("✋👂👃👁️👄🦶😀😢😴🤧👶👦👧👵👴")
];