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
5 changed files with 106 additions and 6 deletions
Showing only changes of commit 629329894d - Show all commits

View file

@ -1 +1 @@
0.4.39-beta
0.4.40-beta

View file

@ -1310,6 +1310,15 @@
.role-preview-note{max-width:760px;margin:0 auto 14px;padding:11px 14px;border:1px solid var(--orange);border-radius:12px;background:color-mix(in srgb,var(--orange) 16%,var(--surface));color:var(--ink);font-size:13px;line-height:1.45}
body.role-preview-active #parentView button:disabled,body.role-preview-active #parentView input:disabled,body.role-preview-active #parentView select:disabled{opacity:.55;cursor:not-allowed}
body.role-preview-pupil #pupilView .widget-body{pointer-events:none;user-select:none}
.role-preview-note .role-preview-add-widget{display:flex;margin-top:9px}
.role-preview-widget-picker{width:min(760px,94vw);max-height:88vh;overflow:auto}
.role-preview-widget-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;margin-top:12px}
.role-preview-widget-card{width:100%;display:grid;grid-template-columns:50px minmax(0,1fr);align-items:center;gap:10px;padding:11px;border:1px solid var(--line);border-radius:14px;background:var(--surface);color:var(--ink);text-align:left;cursor:pointer}
.role-preview-widget-card:hover,.role-preview-widget-card:focus-visible{border-color:var(--accent);outline:2px solid var(--accent-soft)}
.role-preview-widget-card .gi{font-size:30px;text-align:center}.role-preview-widget-card>span:last-child{min-width:0}.role-preview-widget-card .gn,.role-preview-widget-card .gd{display:block}.role-preview-widget-card .gd{margin-top:3px;color:var(--muted);font-size:11px;line-height:1.35}
body.role-preview-updating #board{visibility:hidden}
@media(max-width:560px){.role-preview-widget-grid{grid-template-columns:1fr}}
body.tb-left #rolePreviewBanner,body.tb-right #rolePreviewBanner{top:8px}
@media(max-width:720px){.vw-role-block .vw-row{align-items:stretch;flex-direction:column}.vw-role-seg{max-width:none;justify-content:flex-start}#rolePreviewBanner{max-width:calc(100vw - 24px);white-space:nowrap}#rolePreviewBanner strong{display:none}}

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.39-beta";
const VERSION = "0.4.40-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;
@ -289,6 +289,8 @@ const I18N = {
vwPreview:"Voorbeeldweergave", vwPreviewReset:"Terug naar systeem",
vwParentPreview:"Voorbeeld van het ouderportaal. Echte kindgegevens en acties zijn in deze weergave uitgeschakeld.",
vwPupilPreview:"Voorbeeld van de leerlingomgeving met de taal- en rekenwidgets van het huidige bord. Bediening en voortgang zijn uitgeschakeld.",
vwPupilAddWidget:"Widget toevoegen", vwPupilChooseWidget:"Kies een leerlingwidget",
vwPupilAddWidgetNote:"Alleen taal- en rekenwidgets die in de leerlingomgeving werken worden getoond.",
vwNote:"Deze voorkeuren gelden voor dit apparaat.",
pwChanged:"Wachtwoord gewijzigd ✓", pwCurrentPh:"huidig wachtwoord",
namePh:"naam", passPh:"wachtwoord",
@ -667,6 +669,8 @@ const I18N = {
vwPreview:"Preview mode", vwPreviewReset:"Return to system",
vwParentPreview:"Preview of the parent portal. Real child data and actions are disabled in this view.",
vwPupilPreview:"Preview of the pupil environment using language and maths widgets from the current board. Controls and progress are disabled.",
vwPupilAddWidget:"Add widget", vwPupilChooseWidget:"Choose a pupil widget",
vwPupilAddWidgetNote:"Only language and maths widgets supported in the pupil environment are shown.",
vwNote:"These preferences apply to this device.",
pwChanged:"Password changed ✓", pwCurrentPh:"current password",
namePh:"name", passPh:"password",

View file

@ -14,6 +14,78 @@
parent:"vwRoleParent",pupil:"vwRolePupil"
};
let renderSequence=0;
let pupilWidgetPicker=null;
function closePupilWidgetPicker(){
if(pupilWidgetPicker)pupilWidgetPicker.classList.remove("open");
}
function openPupilWidgetPicker(){
if(!isSystemManager()||activeRole()!=="pupil")return;
if(!pupilWidgetPicker){
pupilWidgetPicker=document.createElement("div");
pupilWidgetPicker.className="modalwrap";
pupilWidgetPicker.id="pupilPreviewWidgetPicker";
pupilWidgetPicker.setAttribute("role","dialog");
pupilWidgetPicker.setAttribute("aria-modal","true");
pupilWidgetPicker.addEventListener("click",event=>{
if(event.target===pupilWidgetPicker)closePupilWidgetPicker();
});
document.body.appendChild(pupilWidgetPicker);
}
const modal=document.createElement("div");
modal.className="modal role-preview-widget-picker";
const head=document.createElement("div");
head.className="gallery-head";
const title=document.createElement("h2");
title.id="pupilPreviewWidgetTitle";
title.textContent=T("vwPupilChooseWidget");
const close=document.createElement("button");
close.type="button";close.className="wclose";close.textContent="✕";
close.title=T("close");close.setAttribute("aria-label",T("close"));
close.addEventListener("click",closePupilWidgetPicker);
head.append(title,close);modal.appendChild(head);
const note=document.createElement("p");
note.className="guestnote";note.textContent=T("vwPupilAddWidgetNote");
modal.appendChild(note);
const host=document.createElement("div");
pupilAllowedDefs().forEach(def=>{
const card=document.createElement("button");
card.type="button";card.className="gcard role-preview-widget-card";
const icon=document.createElement("span");
icon.className="gi";icon.textContent=def.icon;
const text=document.createElement("span");
const name=document.createElement("strong");
name.className="gn";name.textContent=T("wg_"+def.id);
const desc=document.createElement("small");
desc.className="gd";desc.textContent=T("wg_"+def.id+"_d");
text.append(name,desc);card.append(icon,text);
card.addEventListener("click",()=>addPupilPreviewWidget(def));
host.appendChild(card);
});
host.className="role-preview-widget-grid";
modal.appendChild(host);
pupilWidgetPicker.setAttribute("aria-labelledby",title.id);
pupilWidgetPicker.replaceChildren(modal);
pupilWidgetPicker.classList.add("open");
close.focus();
}
async function addPupilPreviewWidget(def){
if(!isSystemManager()||activeRole()!=="pupil"||!pupilAllowedDefs().includes(def))return;
closePupilWidgetPicker();
const previewRole=activeRole();
document.body.classList.add("role-preview-updating");
stopPupilView();
setActiveViewRole("super");
try{
makeWidget(def);
await doSave();
}finally{
setActiveViewRole(previewRole);
document.body.classList.remove("role-preview-updating");
renderPreviewSurface();
}
}
const isSystemManager=()=>!!currentUser&&currentUser.role==="super";
const roleLabel=role=>T(ROLE_KEYS[role]||ROLE_KEYS.super);
@ -39,6 +111,7 @@
renderSequence++;
document.body.classList.remove("role-preview-active","role-preview-parent","role-preview-pupil");
banner.hidden=true;
closePupilWidgetPicker();
if(isSystemManager()){
stopPupilView();
stopParentView();
@ -60,6 +133,7 @@
const sequence=++renderSequence;
if(!isSystemManager()){clearPreviewSurface();syncControls();return;}
const role=activeRole();
closePupilWidgetPicker();
document.body.classList.remove("role-preview-active","role-preview-parent","role-preview-pupil");
stopPupilView();
stopParentView();
@ -74,7 +148,13 @@
const snapshot=serializeBoard();
await renderPupilWidgets(snapshot.widgets||[],"kijken",true);
if(sequence!==renderSequence)return;
pupilView.prepend(previewNotice(T("vwPupilPreview")));
const note=previewNotice(T("vwPupilPreview"));
const add=document.createElement("button");
add.type="button";add.className="tbtn role-preview-add-widget";
add.textContent=" "+T("vwPupilAddWidget");
add.addEventListener("click",openPupilWidgetPicker);
note.appendChild(add);
pupilView.prepend(note);
}
syncControls();
}

View file

@ -38,22 +38,27 @@ test('Weergave bevat de vijf rollen en laadt de veilige voorbeeldmodule vóór d
assert.match(preview,/renderParentView\(\[\]\)/);
assert.match(preview,/renderPupilWidgets\(snapshot\.widgets\|\|\[\],"kijken",true\)/);
assert.match(preview,/querySelectorAll\("button,input,select,textarea"\)/);
assert.match(preview,/pupilAllowedDefs\(\)\.forEach/);
assert.match(preview,/pupilAllowedDefs\(\)\.includes\(def\)/);
assert.match(preview,/setActiveViewRole\("super"\)[\s\S]*?makeWidget\(def\)[\s\S]*?await doSave\(\)/);
assert.match(preview,/role-preview-add-widget/);
assert.doesNotMatch(preview,/currentUser\.role\s*=(?!=)/);
assert.doesNotMatch(preview,/api\(/);
assert.match(css,/body\.role-preview-pupil #pupilView \.widget-body\{pointer-events:none/);
for(const key of ['vwRole','vwRoleSystem','vwRoleAdmin','vwRoleTeacher','vwRoleParent','vwRolePupil','vwRoleNote','vwPreviewReset']){
for(const key of ['vwRole','vwRoleSystem','vwRoleAdmin','vwRoleTeacher','vwRoleParent','vwRolePupil','vwRoleNote','vwPreviewReset','vwPupilAddWidget','vwPupilChooseWidget','vwPupilAddWidgetNote']){
assert.equal((core.match(new RegExp(key+':','g'))||[]).length,2,key);
}
assert.ok(core.includes('const VERSION = "'+version.trim()+'"'));
});
test('voorbeeldrol begrenst clientmenus maar verandert serverpermissies niet',async()=>{
const [client,server,settings,admin,explorer]=await Promise.all([
const [client,server,settings,admin,explorer,pupil]=await Promise.all([
readFile('public/js/permissions.js','utf8'),
readFile('src/permissions.js','utf8'),
readFile('public/js/settings.js','utf8'),
readFile('public/js/admin.js','utf8'),
readFile('public/js/explorer.js','utf8')
readFile('public/js/explorer.js','utf8'),
readFile('public/js/pupil.js','utf8')
]);
assert.match(client,/function activeRoles\(\)/);
assert.doesNotMatch(server,/VIEW_AS_ROLE|activeRole/);
@ -61,4 +66,6 @@ test('voorbeeldrol begrenst clientmenus maar verandert serverpermissies niet'
assert.match(admin,/activeRoles\(\)\.flatMap/);
assert.match(admin,/show:\(\)=>activeRole\(\)==="super"/);
assert.match(explorer,/activeRole\(\) === "pupil"/);
assert.match(pupil,/async function renderPupilWidgets\(widgets, mode, preview=false\)/);
assert.match(pupil,/if\(!preview\)await Promise\.all/);
});