teach/public/js/picker.js
Ramon 270739da09
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 12s
v0.2.16-beta: ankerwoorden - lengtegrens-bug, geen nep-ankersymbool, koppeling met woordkaart
- Lengte-check voor ankerwoorden was strenger dan het invoerveld zelf toestond ({2,12} vs maxLength 24): woorden als "kledingwinkel" werden nooit opgezocht terwijl ARASAAC er prima plaatjes voor heeft
- Geen ankersymbool meer als terugval wanneer ARASAAC geen match heeft (bv. "contant"); de cel blijft leeg en is klikbaar als snelkoppeling naar een nieuwe, herbruikbare plaatjeskiezer (nieuw: public/js/picker.js, hergebruikt dezelfde emoji-lijst/ARASAAC-zoekfunctie/CSS als de bestaande woordkaart-editor)
- Elk ankerraster synchroniseert zijn woorden-met-plaatje voortaan naar een eigen thema in THEMES, zodat ankerwoorden ook beheerbaar zijn via de bestaande woordkaart-editor (Letterblokken-widget) - geen wijziging aan letters.js nodig, het bestaande thema-systeem pakt dit vanzelf op
- Oudere borden met het letterlijke ankersymbool als opgeslagen "plaatje" migreren automatisch naar de nieuwe lege staat

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 19:28:05 +02:00

162 lines
6 KiB
JavaScript

/* teach - herbruikbare plaatjeskiezer (emoji / ARASAAC-zoeken / eigen upload).
Bouwt voort op dezelfde bouwstenen als de woordkaart-editor in letters.js
(EMOJI_CATS, arasaacSearch/arasaacUrl, setPic, dezelfde CSS-klassen) i.p.v.
een tweede plaatjessysteem te maken. Gebruikt door widgets die snel een los
plaatje willen laten kiezen zonder de volledige woordkaart-editor te openen
(bv. Ankerwoorden, als snelkoppeling vanaf een woord zonder automatisch plaatje). */
"use strict";
let pickerWrap = null, pickerCat = 0, pickerOnSelect = null;
function choosePic(pic){
if(pickerOnSelect) pickerOnSelect(pic);
closePicPicker();
}
function closePicPicker(){
if(pickerWrap) pickerWrap.classList.remove("open");
pickerOnSelect = null;
}
function ensurePicPicker(){
if(pickerWrap) return pickerWrap;
const wrap = document.createElement("div");
wrap.className = "modalwrap";
wrap.innerHTML = `
<div class="modal">
<div class="am-head">
<h2 data-i18n="pickPic"></h2>
<button class="wclose pp-close">✕</button>
</div>
<div class="ed-tabs">
<button type="button" data-t="emoji"></button>
<button type="button" data-t="picto"></button>
<button type="button" data-t="upload"></button>
</div>
<div class="ed-pane">
<div class="ed-p-emoji">
<div class="ed-cats"></div>
<div class="ed-grid"></div>
</div>
<div class="ed-p-picto" style="display:none;">
<div class="ed-pictorow">
<input type="text" class="ed-psearch" maxlength="30">
<button class="tbtn ed-pgo"></button>
</div>
<div class="ed-status"></div>
<div class="ed-pgrid"></div>
<div class="ed-credit"></div>
</div>
<div class="ed-p-upload" style="display:none;">
<label class="ed-upload"><span class="ed-uplbl"></span><input type="file" accept="image/*"></label>
<div class="ed-uphint"></div>
</div>
</div>
</div>`;
document.body.appendChild(wrap);
pickerWrap = wrap;
const q = s => wrap.querySelector(s);
wrap.addEventListener("click", e=>{ if(e.target===wrap) closePicPicker(); });
q(".pp-close").addEventListener("click", closePicPicker);
let tab = "emoji";
function setTab(t){ tab = t; refreshTabs(); }
function applyLabels(){
q(".ed-uplbl").textContent = T("upload");
q(".ed-uphint").textContent = T("upHint");
q(".ed-credit").innerHTML = T("pictoCredit");
q(".ed-pgo").textContent = T("pictoSearch");
q(".ed-psearch").placeholder = T("pictoPh");
q(".pp-close").title = T("close");
refreshTabs();
}
function refreshTabs(){
wrap.querySelectorAll(".ed-tabs button").forEach(b=>{
b.textContent = T("tab"+b.dataset.t[0].toUpperCase()+b.dataset.t.slice(1));
b.classList.toggle("active", b.dataset.t===tab);
});
q(".ed-p-emoji").style.display = tab==="emoji" ? "block" : "none";
q(".ed-p-picto").style.display = tab==="picto" ? "block" : "none";
q(".ed-p-upload").style.display = tab==="upload" ? "block" : "none";
}
wrap.querySelectorAll(".ed-tabs button").forEach(b=>{
b.addEventListener("click", ()=>{ tab = b.dataset.t; refreshTabs(); });
});
function renderCats(){
const cats = q(".ed-cats");
cats.innerHTML = "";
T("cats").forEach((name,i)=>{
const b = document.createElement("button");
b.type = "button"; b.textContent = name;
b.classList.toggle("active", i===pickerCat);
b.addEventListener("click", ()=>{ pickerCat=i; renderCats(); });
cats.appendChild(b);
});
const grid = q(".ed-grid");
grid.innerHTML = "";
EMOJI_CATS[pickerCat].forEach(em=>{
const b = document.createElement("button");
b.type = "button"; b.textContent = em;
b.addEventListener("click", ()=>choosePic(em));
grid.appendChild(b);
});
}
async function searchPictos(){
const term = q(".ed-psearch").value.trim();
if(!term) return;
q(".ed-status").textContent = T("pictoLoad");
q(".ed-pgrid").innerHTML = "";
try{
const arr = await arasaacSearch(term, LANG);
if(!arr.length){ q(".ed-status").textContent = T("pictoNone"); return; }
q(".ed-status").textContent = "";
arr.slice(0,12).forEach(p=>{
const url = arasaacUrl(p._id);
const b = document.createElement("button");
b.type = "button";
const im = document.createElement("img");
im.src = url; im.loading = "lazy"; im.alt = term;
b.appendChild(im);
b.addEventListener("click", ()=>choosePic(url));
q(".ed-pgrid").appendChild(b);
});
}catch(e){ q(".ed-status").textContent = T("pictoErr"); }
}
q(".ed-pgo").addEventListener("click", searchPictos);
q(".ed-psearch").addEventListener("keydown", e=>{ if(e.key==="Enter") searchPictos(); });
q(".ed-p-upload input").addEventListener("change", e=>{
const f = e.target.files[0]; if(!f) return;
const img = new Image();
img.onload = ()=>{
const c = document.createElement("canvas");
const s = Math.min(1, 128/Math.max(img.width, img.height));
c.width = Math.round(img.width*s); c.height = Math.round(img.height*s);
c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
choosePic(c.toDataURL("image/png"));
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(f);
e.target.value = "";
});
wrap.__applyLabels = applyLabels;
wrap.__renderCats = renderCats;
wrap.__searchPictos = searchPictos;
wrap.__setTab = setTab;
return wrap;
}
function openPicPicker(word, onSelect){
const wrap = ensurePicPicker();
pickerOnSelect = onSelect;
wrap.__applyLabels();
wrap.__renderCats();
wrap.querySelector(".ed-psearch").value = word || "";
/* met een woord starten we meteen op het picto-zoekresultaat i.p.v. het emoji-tabblad,
anders lijkt de vooraf ingevulde zoekopdracht nergens naartoe te leiden */
wrap.__setTab(word ? "picto" : "emoji");
wrap.classList.add("open");
if(word) wrap.__searchPictos();
}