Verbeter anatomie en plaatsing van avataraccessoires (v0.4.46-beta) #1

Open
bes-r wants to merge 192 commits from bes-r/avatar-realism into main AGit
8 changed files with 104 additions and 12 deletions
Showing only changes of commit 17ee47dd09 - Show all commits

View file

@ -1 +1 @@
0.4.41-beta
0.4.42-beta

View file

@ -1523,6 +1523,11 @@ body.tb-left #rolePreviewBanner,body.tb-right #rolePreviewBanner{top:8px}
.managed-learning-level .clock-level-control,
.managed-learning-level .place-level-control,
.managed-learning-level .money-level-control{display:none;}
.board-learning-level{display:grid;grid-template-columns:auto auto minmax(130px,1fr);align-items:end;gap:8px;padding:8px 10px;border-bottom:1px solid var(--line);background:color-mix(in srgb,var(--accent) 7%,var(--surface));font-size:11px}
.board-learning-level label{display:grid;gap:3px;color:var(--muted);font-weight:800}
.board-learning-select{min-width:104px;border:1px solid var(--line);border-radius:8px;padding:6px 8px;background:var(--surface);color:var(--ink);font:inherit;font-weight:750}
.board-learning-summary{align-self:center;overflow:hidden;color:var(--muted);font-weight:700;text-overflow:ellipsis;white-space:nowrap}
@media(max-width:620px){.board-learning-level{grid-template-columns:1fr 1fr}.board-learning-summary{grid-column:1/-1}.board-learning-select{width:100%;min-width:0}}
@media(max-width:620px){.learning-level-card .am-inline{align-items:stretch;flex-direction:column;}}
/* gevaarlijke acties: afwijkende kleur + tweede klik als bevestiging */
.am-btn.am-danger{background:var(--red-soft); color:var(--red); font-weight:800;}

View file

@ -355,15 +355,43 @@ function makeWidget(def, saved){
board.appendChild(win);
/* Op het eigen bord van een thuiskind is de centrale leerjaarinstelling
leidend. Staf- en gastborden behouden exact hun opgeslagen widgetstate. */
const mountedState = typeof learningStateForWidget === "function"
leidend. Leerkracht en systeemmanager krijgen hieronder een opgeslagen
keuze per widget; overige borden behouden hun bestaande widgetstate. */
const initialState = typeof learningStateForWidget === "function"
? learningStateForWidget(def, saved ? saved.state : null)
: (saved ? saved.state : null);
const api = def.mount(body, mountedState) || {};
if(typeof appendLearningProfileBadge === "function" && mountedState &&
["taal","rekenen"].includes(def.cat)){
appendLearningProfileBadge(body, def.cat, mountedState, def.id);
}
let boardLearning = typeof boardLearningSelection === "function"
? boardLearningSelection(def, initialState) : null;
let mountedApi = {};
const mountContent = state=>{
body.replaceChildren();
const mountedState = typeof boardLearningMountState === "function"
? boardLearningMountState(def, state, boardLearning) : state;
mountedApi = def.mount(body, mountedState) || {};
if(typeof appendLearningProfileBadge === "function" && mountedState &&
["taal","rekenen"].includes(def.cat)){
appendLearningProfileBadge(body, def.cat, mountedState, def.id);
}
if(boardLearning && typeof appendBoardLearningLevelControls === "function"){
appendBoardLearningLevelControls(body,def,boardLearning,next=>{
const live=mountedApi.getState?mountedApi.getState():{};
boardLearning=next;
mountContent(Object.assign({},live,next,{managedLearningLevel:false}));
markBoardChange();
});
}
};
mountContent(initialState);
/* Het widget-API blijft stabiel terwijl een niveauwissel de inhoud opnieuw
mount. De auteurskeuze wordt hier terug aan de widgetstate toegevoegd;
widgetmodules laten leerlinggebonden velden zelf bewust buiten getState. */
const api = {
getState:()=>{
const state=mountedApi.getState?mountedApi.getState():null;
return boardLearning?Object.assign({},state||{},boardLearning,{managedLearningLevel:false}):state
},
setState:state=>{mountContent(state)}
};
if(saved && saved.min) setMin(true);
if(saved && saved.max) setMax(true);
const inst = { def, win, api, wid: (saved && saved.wid) || genWidgetId() };

View file

@ -2,7 +2,7 @@
"use strict";
/* 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. */
const VERSION = "0.4.41-beta";
const VERSION = "0.4.42-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;

View file

@ -106,6 +106,39 @@ function learningStateForWidget(def,state){
if(!currentUser||currentUser.role!=="pupil"||currentUser.schoolId!=null)return state;
return applyLearningLevelToState(def,state,SELF_LEARNING_LEVELS[def.cat],true)
}
/* Op het eigen auteursbord kiest de leerkracht/systeemmanager een leerjaar en
differentiatie per widget. Een systeemmanager blijft eigenaar van dit bord
in de systeem-, schoolbeheerder- en leerkrachtweergave. Ouder- en
leerlingvoorbeelden gebruiken een aparte, alleen-lezen voorbeeldweergave. */
function canConfigureBoardLearningLevel(def){
if(!def||!["taal","rekenen"].includes(def.cat)||!currentUser)return false;
const role=typeof activeRole==="function"?activeRole():currentUser.role;
return currentUser.role==="teacher"||
(currentUser.role==="super"&&["super","admin","teacher"].includes(role))
}
function boardLearningSelection(def,state){
if(!canConfigureBoardLearningLevel(def))return null;
const savedLevel=state&&state.level!=null?Number(state.level):null;
return{yearGroup:learningYearGroup(state&&state.yearGroup)||4,level:Number.isInteger(savedLevel)?learningLevel(savedLevel):2}
}
function boardLearningMountState(def,state,selection){
return selection?applyLearningLevelToState(def,state,selection,true):state
}
function appendBoardLearningLevelControls(root,def,selection,onChange){
if(!root||!selection||typeof onChange!=="function")return null;
const bar=document.createElement("div");bar.className="board-learning-level";
const yearLabel=document.createElement("label"),yearText=document.createElement("span"),year=document.createElement("select");
const levelLabel=document.createElement("label"),levelText=document.createElement("span"),level=document.createElement("select");
const summary=document.createElement("span");summary.className="board-learning-summary";
year.className=level.className="board-learning-select";
for(let n=1;n<=8;n++)year.add(new Option((LANG==="nl"?"Groep ":"Year ")+n,n));
for(let n=1;n<=3;n++)level.add(new Option(learningLevelLabel(n),n));
year.value=selection.yearGroup;level.value=selection.level;
const labels=()=>{yearText.textContent=LANG==="nl"?"Leerjaar":"Year";levelText.textContent=LANG==="nl"?"Niveau":"Level";summary.textContent=learningProfileSummary(def.cat,+year.value,+level.value,def.id)};
const changed=()=>{labels();onChange({yearGroup:+year.value,level:+level.value})};
year.addEventListener("change",changed);level.addEventListener("change",changed);
yearLabel.append(yearText,year);levelLabel.append(levelText,level);bar.append(yearLabel,levelLabel,summary);labels();root.prepend(bar);return bar
}
function appendLearningProfileBadge(root,subject,state,widgetId){
const summary=learningProfileSummary(subject,state&&state.yearGroup,state&&state.level,widgetId);if(!summary)return;
const old=root.querySelector(":scope > .learning-profile-badge");if(old)old.remove();

View file

@ -648,6 +648,8 @@ function field(label, el){const w=document.createElement('label');const s=docume
function btn(text,fn,kind='ghost'){const b=document.createElement('button');b.type='button';b.className='tbtn '+kind;b.textContent=text;b.onclick=fn;return b}
function num(value,min,max){const i=document.createElement('input');i.type='number';i.value=value;i.min=min;i.max=max;return i}
function persistedLearningState(state,extra={}){
/* Leerlinggebonden velden worden niet door de widget zelf bewaard. board.js
voegt op het auteursbord de expliciete leerjaarkeuze na getState weer toe. */
const copy={...state,...extra};delete copy.yearGroup;delete copy.managedLearningLevel;return copy
}

View file

@ -8,9 +8,8 @@ function mountMath(root, initState, mode, opts){
const S = { level:1, yearGroup:null, managedLearningLevel:false, snd:true, amode:"blocks", q:null, tries:0, done:0, good:0, input:"", busy:false };
if(initState){
S.level = initState.level||1;
/* yearGroup komt van klassenmanagement (pupil.js), niet van het bord zelf
- bewust niet in getState() opgenomen, dit is een per-leerling waarde,
geen door de leerkracht op het bord ingestelde inhoud. */
/* yearGroup komt bij leerlingen van klassenmanagement (pupil.js). Op een
auteursbord bewaart board.js de aparte leerjaarkeuze rondom getState(). */
S.yearGroup = initState.yearGroup || null;
S.managedLearningLevel = !!initState.managedLearningLevel;
S.snd = initState.snd!==false;

View file

@ -28,6 +28,28 @@ test('taal en rekenen hebben een compleet inhoudelijk profiel voor groep 1 tot e
}
});
test('alleen leerkracht en systeemmanager kunnen het leerjaar op hun eigen bord kiezen',async()=>{
const source=await readFile('public/js/learning-progressions.js','utf8');
const pure=source.slice(0,source.indexOf('(function registerOwnLearningLevelSettings'));
const context={LANG:'nl',currentUser:{role:'teacher'}};
context.activeRole=()=>context.currentUser.role;
runInNewContext(pure+';globalThis.__api={canConfigureBoardLearningLevel,boardLearningSelection}',context);
const def={cat:'taal'};
assert.equal(context.__api.canConfigureBoardLearningLevel(def),true);
assert.deepEqual(JSON.parse(JSON.stringify(context.__api.boardLearningSelection(def,null))),{yearGroup:4,level:2});
assert.deepEqual(JSON.parse(JSON.stringify(context.__api.boardLearningSelection(def,{}))),{yearGroup:4,level:2});
assert.deepEqual(JSON.parse(JSON.stringify(context.__api.boardLearningSelection(def,{yearGroup:7,level:3}))),{yearGroup:7,level:3});
context.currentUser={role:'super'};
assert.equal(context.__api.canConfigureBoardLearningLevel({cat:'rekenen'}),true);
context.activeRole=()=>'teacher';
assert.equal(context.__api.canConfigureBoardLearningLevel(def),true);
context.activeRole=()=>'pupil';
assert.equal(context.__api.canConfigureBoardLearningLevel(def),false);
context.currentUser={role:'admin'};
context.activeRole=()=>context.currentUser.role;
assert.equal(context.__api.canConfigureBoardLearningLevel(def),false);
});
test('taalprofiel selecteert eenvoudiger woorden in de onderbouw en bouwt taaksteun af',async()=>{
const c=await progressionContext();
const words=[['kat','x'],['vis','x'],['pen','x'],['maan','x'],['boek','x'],['fiets','x'],
@ -62,6 +84,9 @@ test('alle taal- en rekenwidgets ontvangen de centrale leerjaarstate en tonen he
assert.ok(html.indexOf('js/data.js')<html.indexOf('js/learning-progressions.js'));
assert.ok(html.indexOf('js/learning-progressions.js')<html.indexOf('js/widgets/letters.js'));
assert.match(board,/learningStateForWidget/);
assert.match(board,/boardLearningSelection/);
assert.match(board,/appendBoardLearningLevelControls/);
assert.match(board,/managedLearningLevel:false/);
assert.match(pupil,/applyLearningLevelToState/);
assert.match(pupil,/appendLearningProfileBadge/);
assert.match(progressions,/managedLearningLevel/);