teach/public/js/cast.js
Ramon ae8d0d4bb4
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 1m23s
feat: voeg leerpaden dashboard en avatarbeloningen toe
2026-07-23 21:23:54 +02:00

199 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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 - cast (📡): het geopende bord (of één widget erop) rechtstreeks
vanaf de toolbar uitzenden naar een klas of leerling, i.p.v. de omweg via
Instellingen > Toewijzingen. Hergebruikt dezelfde PUT/DELETE-routes als
admin.js's assignmentRow - geen serverwijziging, alleen een korter pad. */
"use strict";
const btnCast = document.getElementById("btnCast");
btnCast.title = T("cast");
document.addEventListener("langchange", ()=>{
btnCast.title = T("cast");
if(castOpen) renderCast();
});
function h(tag, cls, text){
const el = document.createElement(tag);
if(cls) el.className = cls;
if(text != null) el.textContent = text;
return el;
}
const castBackdrop = h("div","am-sheet-backdrop");
castBackdrop.id = "castBackdrop";
document.body.appendChild(castBackdrop);
castBackdrop.addEventListener("click", e=>{ if(e.target===castBackdrop) closeCast(); });
let castOpen = false;
let castLoading = false;
let castClasses = [], castPupils = [];
let castSelWidget = "", castSelMode = "werken";
let castPathTitle = "", castPathSteps = [];
/* toewijzingen die deze sessie al gelukt zijn ("type:id"), zodat je hetzelfde
bord naar meerdere klassen/leerlingen achter elkaar kunt casten zonder dat
de sheet na elke keer sluit */
let castActiveKeys = new Set();
let castError = "";
/* widgets van het live-open bord, zelfde bron als serializeBoard() (board.js) */
function currentBoardWidgets(){
return [...instances].map(inst=>({ wid: inst.wid, id: inst.def.id, cat: inst.def.cat })).filter(w=>w.wid);
}
function castPathWidgetAllowed(widgetId){
const widget = currentBoardWidgets().find(item=>item.wid===widgetId);
return !!widget && ["taal","rekenen"].includes(widget.cat);
}
btnCast.addEventListener("click", async ()=>{
stashCurrent(); /* verse widget-state, zelfde als btnFolders doet */
castOpen = true;
castSelWidget = ""; castSelMode = "werken";
castPathTitle = ""; castPathSteps = [];
castActiveKeys = new Set(); castError = "";
castLoading = true;
renderCast();
try{
const [cRes, uRes] = await Promise.all([ api("/admin/classes"), api("/admin/users") ]);
castClasses = cRes.classes || [];
castPupils = (uRes.users || []).filter(u=>u.role==="pupil");
}catch(e){
castClasses = []; castPupils = []; castError = e.message;
}
castLoading = false;
renderCast();
});
function closeCast(){ castOpen = false; renderCast(); }
function castTargetRow(target){
const row = h("div","am-row am-target");
const info = h("div","am-person-info");
info.appendChild(h("span","am-name", target.label));
if(target.sub) info.appendChild(h("span","am-role", target.sub));
row.appendChild(info);
const key = target.type+":"+target.id;
if(castActiveKeys.has(key)) row.appendChild(h("span","am-chip active", T("castActive")));
row.addEventListener("click", async ()=>{
try{
const body = { boardId: curSlot().id };
if(castPathSteps.length){
body.title = castPathTitle.trim();
body.sequence = castPathSteps.map(step=>({boardId:step.boardId, widgetId:step.widgetId, mode:step.mode}));
}else if(castSelWidget){ body.widgetId = castSelWidget; body.mode = castSelMode; }
await api(`/assignments/${target.type}/${target.id}`, { method:"PUT", body });
castActiveKeys.add(key);
castError = "";
}catch(e){ castError = e.message; }
renderCast();
});
return row;
}
function renderCast(){
castBackdrop.classList.toggle("open", castOpen);
castBackdrop.innerHTML = "";
if(!castOpen) return;
const sheet = h("div","am-sheet");
castBackdrop.appendChild(sheet);
const head = h("div","am-kebab-row");
const titleWrap = h("div");
titleWrap.appendChild(h("h2","am-nav-title", T("castTitle")));
titleWrap.appendChild(h("div","am-role", `${BS.folders[BS.f].name} / ${curSlot().name}`));
head.appendChild(titleWrap);
const closeBtn = h("button","am-btn", T("close"));
closeBtn.type = "button";
closeBtn.addEventListener("click", closeCast);
head.appendChild(closeBtn);
sheet.appendChild(head);
sheet.appendChild(h("div","am-msg", castError));
sheet.appendChild(h("div","am-group", T("castWhat")));
const widgetSel = h("select","am-sel");
widgetSel.appendChild(new Option(T("amWholeBoard"), ""));
currentBoardWidgets().forEach(w=>widgetSel.appendChild(new Option(T("wg_"+w.id), w.wid)));
widgetSel.value = castSelWidget;
const modeSel = h("select","am-sel");
modeSel.appendChild(new Option(T("amModeWork"), "werken"));
modeSel.appendChild(new Option(T("amModeWatch"), "kijken"));
modeSel.value = castSelMode;
modeSel.style.display = castSelWidget ? "" : "none";
widgetSel.addEventListener("change", ()=>{
castSelWidget = widgetSel.value;
modeSel.style.display = castSelWidget ? "" : "none";
addStep.disabled = !castPathWidgetAllowed(castSelWidget) || castPathSteps.length >= 12;
});
modeSel.addEventListener("change", ()=>{ castSelMode = modeSel.value; });
const whatRow = h("div","am-inline");
whatRow.appendChild(widgetSel);
whatRow.appendChild(modeSel);
sheet.appendChild(whatRow);
sheet.appendChild(h("div","am-group", T("castPathTitle")));
const pathTitle = h("input","am-inp");
pathTitle.type = "text";
pathTitle.maxLength = 80;
pathTitle.placeholder = T("castPathPlaceholder");
pathTitle.value = castPathTitle;
pathTitle.addEventListener("input", ()=>{ castPathTitle = pathTitle.value; castActiveKeys.clear(); });
sheet.appendChild(pathTitle);
const addStep = h("button","am-btn", T("castPathAdd"));
addStep.type = "button";
addStep.disabled = !castPathWidgetAllowed(castSelWidget) || castPathSteps.length >= 12;
addStep.title = T("castPathOnlyLearning");
addStep.addEventListener("click", ()=>{
if(!castPathWidgetAllowed(castSelWidget) || castPathSteps.length >= 12) return;
const widget = currentBoardWidgets().find(item=>item.wid===castSelWidget);
castPathSteps.push({boardId:curSlot().id, widgetId:castSelWidget, widgetType:widget?.id || "", mode:castSelMode});
if(!castPathTitle) castPathTitle = T("castPathDefault");
castActiveKeys.clear();
renderCast();
});
sheet.appendChild(addStep);
if(castPathSteps.length){
const pathList = h("div","cast-path-list");
castPathSteps.forEach((step,index)=>{
const row = h("div","cast-path-step");
row.appendChild(h("span","cast-path-number", String(index+1)));
const label = h("span","am-name", T("wg_"+step.widgetType));
label.appendChild(h("small","am-role", " · "+T(step.mode==="kijken"?"amModeWatch":"amModeWork")));
row.appendChild(label);
const up = h("button","am-btn","↑"); up.type="button"; up.disabled=index===0;
up.addEventListener("click",()=>{ [castPathSteps[index-1],castPathSteps[index]]=[castPathSteps[index],castPathSteps[index-1]]; castActiveKeys.clear(); renderCast(); });
const down = h("button","am-btn","↓"); down.type="button"; down.disabled=index===castPathSteps.length-1;
down.addEventListener("click",()=>{ [castPathSteps[index+1],castPathSteps[index]]=[castPathSteps[index],castPathSteps[index+1]]; castActiveKeys.clear(); renderCast(); });
const remove = h("button","am-btn","×"); remove.type="button";
remove.setAttribute("aria-label",T("amDelete"));
remove.addEventListener("click",()=>{ castPathSteps.splice(index,1); castActiveKeys.clear(); renderCast(); });
row.append(up,down,remove);
pathList.appendChild(row);
});
sheet.appendChild(pathList);
sheet.appendChild(h("div","guestnote", T("castPathHint").replace("{count}",castPathSteps.length)));
}
sheet.appendChild(h("div","am-group", T("castClasses")));
const classList = h("div","am-list");
if(castLoading) classList.appendChild(h("div","guestnote", "…"));
else if(!castClasses.length) classList.appendChild(h("div","guestnote", T("castNoClasses")));
else castClasses.forEach(c=>classList.appendChild(castTargetRow({type:"class", id:c.id, label:c.name})));
sheet.appendChild(classList);
sheet.appendChild(h("div","am-group", T("castPupils")));
const pupilList = h("div","am-list");
if(castLoading) pupilList.appendChild(h("div","guestnote", "…"));
else if(!castPupils.length) pupilList.appendChild(h("div","guestnote", T("castNoPupils")));
else{
const byClass = new Map();
castPupils.forEach(u=>{
const key = u.classId || "none";
if(!byClass.has(key)) byClass.set(key, []);
byClass.get(key).push(u);
});
castClasses.forEach(c=>(byClass.get(c.id) || []).forEach(u=>
pupilList.appendChild(castTargetRow({type:"pupil", id:u.id, label:u.displayName, sub:c.name}))));
(byClass.get("none") || []).forEach(u=>
pupilList.appendChild(castTargetRow({type:"pupil", id:u.id, label:u.displayName, sub:T("amUnassigned")})));
}
sheet.appendChild(pupilList);
}