Voeg 28 herkenbare kleurplaten en creatieve niveaus toe (v0.4.66-beta) #4
8 changed files with 104 additions and 12 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.4.41-beta
|
||||
0.4.42-beta
|
||||
|
|
|
|||
|
|
@ -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;}
|
||||
|
|
|
|||
|
|
@ -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() };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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/);
|
||||
|
|
|
|||
Loading…
Reference in a new issue