Voeg thematische kleurplaten en creatieve niveaus toe (v0.4.65-beta) #3
5 changed files with 178 additions and 18 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.3.87-beta
|
0.3.88-beta
|
||||||
|
|
|
||||||
|
|
@ -2004,3 +2004,42 @@ body.hc #settingsModal .am-inp, body.hc #settingsModal .am-sel{border-color:#000
|
||||||
padding:14px; max-height:none; box-shadow:var(--shadow-2);
|
padding:14px; max-height:none; box-shadow:var(--shadow-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- groepskaart: één kader om items die bij elkaar horen ---- */
|
||||||
|
#settingsModal .am-card{
|
||||||
|
border:1px solid var(--line); border-radius:var(--radius-s); background:var(--surface);
|
||||||
|
overflow:hidden; margin-bottom:10px;
|
||||||
|
}
|
||||||
|
#settingsModal .am-card-head{
|
||||||
|
display:flex; gap:9px; align-items:center; padding:9px 12px;
|
||||||
|
background:var(--surface-2); border-bottom:1px solid var(--line);
|
||||||
|
}
|
||||||
|
#settingsModal .am-card:has(.am-card-subs[style*="none"]):not(:has(.am-card-tools:not([style*="none"]))) .am-card-head{border-bottom:none;}
|
||||||
|
#settingsModal .am-ico{filter:grayscale(1); opacity:.55; font-size:13px; flex:none;}
|
||||||
|
#settingsModal .am-card-title{font-weight:650; font-size:13.5px; flex:1; min-width:80px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
|
||||||
|
#settingsModal .am-card-sum{margin-left:auto; flex:none; font-size:12px; color:var(--muted);}
|
||||||
|
#settingsModal .am-card-toggle{cursor:pointer; user-select:none; padding:4px 6px; border-radius:5px;}
|
||||||
|
#settingsModal .am-card-toggle:hover{background:var(--surface-3); color:var(--ink);}
|
||||||
|
#settingsModal .am-card-tools{padding:10px 12px; border-bottom:1px solid var(--line); display:flex; flex-wrap:wrap; gap:10px;}
|
||||||
|
#settingsModal .am-card-subs > .am-row{border-bottom:1px solid var(--line);}
|
||||||
|
#settingsModal .am-card-subs > .am-row:last-child{border-bottom:none;}
|
||||||
|
/* compacte subrij binnen een kaart */
|
||||||
|
#settingsModal .am-card-sub{font-size:12.5px; padding:5px 12px 5px 30px; flex-wrap:wrap;}
|
||||||
|
#settingsModal .am-card-sub .am-name{font-size:12.5px; font-weight:550; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;}
|
||||||
|
#settingsModal .am-card-sub .am-role{font-size:11.5px; margin-left:auto;}
|
||||||
|
#settingsModal .am-card-sub .am-edit{flex:none;}
|
||||||
|
#settingsModal .am-card-sub .am-row-tools{width:100%;}
|
||||||
|
#settingsModal .img-quota-pick{margin-bottom:8px; max-width:420px;}
|
||||||
|
/* status-glyph (● actief / ○ wacht) met uitleg via title */
|
||||||
|
#settingsModal .am-glyph{flex:none; font-size:10px; color:var(--muted); cursor:default;}
|
||||||
|
#settingsModal .am-glyph.ok{color:var(--green);}
|
||||||
|
/* mobiel: rijen binnen het beheer niet meer verticaal stapelen - de generieke
|
||||||
|
stapelregel maakte school en subrij ononderscheidbaar; wrap vangt overloop */
|
||||||
|
@media (max-width:600px){
|
||||||
|
#settingsModal .am-row{flex-direction:row; align-items:center; flex-wrap:wrap;}
|
||||||
|
/* kaartkop mag wrappen: titel houdt regel 1, samenvatting+Bewerken eronder */
|
||||||
|
#settingsModal .am-card-head{flex-wrap:wrap;}
|
||||||
|
/* compacte subrij op telefoon: naam + Bewerken op regel 1, verbruik eronder */
|
||||||
|
#settingsModal .am-card-sub .am-role{order:3; flex-basis:100%; margin-left:0;}
|
||||||
|
#settingsModal .am-card-sub .am-edit{margin-left:auto;}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,63 @@
|
||||||
return badge;
|
return badge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- groepskaart: één kader om wat bij elkaar hoort ------------------------
|
||||||
|
Kop (grijs icoon + titel + samenvatting-als-toggle + Bewerken) met daaronder
|
||||||
|
optioneel uitklapbare compacte subrijen en/of een uitklapbaar bewerkblok.
|
||||||
|
De open-status leeft in een Set die re-renders overleeft; andere modules
|
||||||
|
(bv. de Opslag-tab in image-catalog.js) geven hun eigen Set mee. */
|
||||||
|
function groupCard(opts){
|
||||||
|
const openSet = opts.openSet || openGroups;
|
||||||
|
const editSet = opts.editSet || openRows;
|
||||||
|
const card = h("div","am-card");
|
||||||
|
const head = h("div","am-card-head");
|
||||||
|
card.appendChild(head);
|
||||||
|
if(opts.icon) head.appendChild(h("span","am-ico", opts.icon));
|
||||||
|
head.appendChild(h("span","am-card-title", opts.title));
|
||||||
|
const sum = h("span","am-card-sum");
|
||||||
|
head.appendChild(sum);
|
||||||
|
let tools = null;
|
||||||
|
if(opts.edit){
|
||||||
|
tools = h("div","am-card-tools");
|
||||||
|
tools.appendChild(opts.edit.tools);
|
||||||
|
const btn = h("button","am-edit", T("amEdit"));
|
||||||
|
btn.type = "button";
|
||||||
|
const ekey = opts.edit.key;
|
||||||
|
const esync = ()=>{
|
||||||
|
const open = editSet.has(ekey);
|
||||||
|
tools.style.display = open ? "" : "none";
|
||||||
|
btn.classList.toggle("on", open);
|
||||||
|
};
|
||||||
|
btn.addEventListener("click", ()=>{
|
||||||
|
if(editSet.has(ekey)) editSet.delete(ekey); else editSet.add(ekey);
|
||||||
|
esync();
|
||||||
|
});
|
||||||
|
esync();
|
||||||
|
head.appendChild(btn);
|
||||||
|
card.appendChild(tools);
|
||||||
|
}
|
||||||
|
if(opts.subs && opts.subs.length){
|
||||||
|
const subHost = h("div","am-card-subs");
|
||||||
|
opts.subs.forEach(el=>subHost.appendChild(el));
|
||||||
|
card.appendChild(subHost);
|
||||||
|
sum.classList.add("am-card-toggle");
|
||||||
|
const sync = ()=>{
|
||||||
|
const open = openSet.has(opts.key);
|
||||||
|
sum.textContent = `${opts.summary} ${open ? "▾" : "▸"}`;
|
||||||
|
subHost.style.display = open ? "" : "none";
|
||||||
|
};
|
||||||
|
sum.addEventListener("click", ()=>{
|
||||||
|
if(openSet.has(opts.key)) openSet.delete(opts.key); else openSet.add(opts.key);
|
||||||
|
sync();
|
||||||
|
});
|
||||||
|
sync();
|
||||||
|
}else if(opts.summary){
|
||||||
|
sum.textContent = opts.summary;
|
||||||
|
}
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
window.amGroupCard = groupCard;
|
||||||
|
|
||||||
/* ---- beheer-secties in het instellingen-register (settings.js) ------------
|
/* ---- beheer-secties in het instellingen-register (settings.js) ------------
|
||||||
Elke sectie meldt zichzelf aan met rol-zichtbaarheid, een activate() en
|
Elke sectie meldt zichzelf aan met rol-zichtbaarheid, een activate() en
|
||||||
zoekgegevens; de schil bouwt daar navigatie + zoeken uit. Per rol alleen
|
zoekgegevens; de schil bouwt daar navigatie + zoeken uit. Per rol alleen
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
/* 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. */
|
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||||
const VERSION = "0.3.87-beta";
|
const VERSION = "0.3.88-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
@ -190,6 +190,8 @@ const I18N = {
|
||||||
imgLoading:"Afbeeldingen laden…", imgSaved:"Afbeelding opgeslagen ✓", imgDeleted:"Afbeelding verwijderd ✓",
|
imgLoading:"Afbeeldingen laden…", imgSaved:"Afbeelding opgeslagen ✓", imgDeleted:"Afbeelding verwijderd ✓",
|
||||||
imgThemeAdded:"Thema toegevoegd ✓", imgStorageAdmin:"Opslaglimieten afbeeldingen",
|
imgThemeAdded:"Thema toegevoegd ✓", imgStorageAdmin:"Opslaglimieten afbeeldingen",
|
||||||
imgQuotaUsage:"{used} gebruikt · {limit}", imgQuotaDefault:"Standaard", imgQuotaCustom:"Aangepast",
|
imgQuotaUsage:"{used} gebruikt · {limit}", imgQuotaDefault:"Standaard", imgQuotaCustom:"Aangepast",
|
||||||
|
imgPickSchool:"— kies een school —", imgSystemScope:"Systeem (zonder school)",
|
||||||
|
imgQuotaDefaultOf:"Standaard ({limit})",
|
||||||
imgQuotaMb:"limiet in MB", imgQuotaInvalid:"Vul een geldige limiet in.", imgSchools:"Scholen",
|
imgQuotaMb:"limiet in MB", imgQuotaInvalid:"Vul een geldige limiet in.", imgSchools:"Scholen",
|
||||||
imgUsers:"Gebruikers", imgSearchUsers:"Zoek gebruiker…",
|
imgUsers:"Gebruikers", imgSearchUsers:"Zoek gebruiker…",
|
||||||
cats:["Dieren","Eten","Natuur","Vervoer","Spullen","Lichaam"],
|
cats:["Dieren","Eten","Natuur","Vervoer","Spullen","Lichaam"],
|
||||||
|
|
@ -482,6 +484,8 @@ const I18N = {
|
||||||
imgLoading:"Loading images…", imgSaved:"Image saved ✓", imgDeleted:"Image deleted ✓",
|
imgLoading:"Loading images…", imgSaved:"Image saved ✓", imgDeleted:"Image deleted ✓",
|
||||||
imgThemeAdded:"Theme added ✓", imgStorageAdmin:"Image storage limits",
|
imgThemeAdded:"Theme added ✓", imgStorageAdmin:"Image storage limits",
|
||||||
imgQuotaUsage:"{used} used · {limit}", imgQuotaDefault:"Default", imgQuotaCustom:"Custom",
|
imgQuotaUsage:"{used} used · {limit}", imgQuotaDefault:"Default", imgQuotaCustom:"Custom",
|
||||||
|
imgPickSchool:"— pick a school —", imgSystemScope:"System (no school)",
|
||||||
|
imgQuotaDefaultOf:"Default ({limit})",
|
||||||
imgQuotaMb:"limit in MB", imgQuotaInvalid:"Enter a valid limit.", imgSchools:"Schools",
|
imgQuotaMb:"limit in MB", imgQuotaInvalid:"Enter a valid limit.", imgSchools:"Schools",
|
||||||
imgUsers:"Users", imgSearchUsers:"Search user…",
|
imgUsers:"Users", imgSearchUsers:"Search user…",
|
||||||
cats:["Animals","Food","Nature","Transport","Things","Body"],
|
cats:["Animals","Food","Nature","Transport","Things","Body"],
|
||||||
|
|
|
||||||
|
|
@ -344,19 +344,28 @@ async function openImageCatalog(onSelect){
|
||||||
|
|
||||||
/* Systeembeheer: limieten per gebruiker en school. Wordt door admin.js als
|
/* Systeembeheer: limieten per gebruiker en school. Wordt door admin.js als
|
||||||
extra Instellingen-tab aangeroepen. */
|
extra Instellingen-tab aangeroepen. */
|
||||||
|
/* Opslag-tab: eerst een school kiezen (of "Systeem" voor school-loze accounts),
|
||||||
|
daarna één groepskaart met de schoolquota in de kop en de gebruikers van die
|
||||||
|
school als compacte subrijen. Bewerkvelden zijn verborgen tot "Bewerken".
|
||||||
|
Keuze en open-status leven op moduleniveau: de save-handler en admin-render()
|
||||||
|
herbouwen het paneel en mogen niets vergeten. */
|
||||||
|
let imageQuotaSchool = "";
|
||||||
|
const imageQuotaOpen = new Set();
|
||||||
|
let imageQuotaFilter = "";
|
||||||
function renderImageStoragePanel(panel){
|
function renderImageStoragePanel(panel){
|
||||||
panel.innerHTML="";
|
panel.innerHTML="";
|
||||||
const heading=document.createElement("div");heading.className="am-h3";heading.textContent=T("imgStorageAdmin");
|
const status=document.createElement("div");status.className="am-msg";panel.append(status);
|
||||||
const status=document.createElement("div");status.className="am-msg";panel.append(heading,status);
|
|
||||||
const host=document.createElement("div");host.className="img-quota-admin";host.textContent=T("imgLoading");panel.append(host);
|
const host=document.createElement("div");host.className="img-quota-admin";host.textContent=T("imgLoading");panel.append(host);
|
||||||
const fmt=value=>value==null?T("imgUnlimited"):imageBytes(value);
|
const fmt=value=>value==null?T("imgUnlimited"):imageBytes(value);
|
||||||
const editor=(item,type)=>{
|
/* het bewerkblok (limiet-keuze + opslaan) achter de Bewerken-knop per kaart/rij */
|
||||||
const row=document.createElement("div");row.className="am-row img-quota-row";
|
const editorTools=(item,type)=>{
|
||||||
const info=document.createElement("div");info.className="am-name";info.textContent=item.name||item.displayName;
|
const wrap=document.createElement("div");wrap.className="am-inline";
|
||||||
const use=document.createElement("span");use.className="am-role";use.textContent=T("imgQuotaUsage").replace("{used}",imageBytes(item.usedBytes)).replace("{limit}",fmt(item.limitBytes));
|
|
||||||
info.append(document.createElement("br"),use);
|
|
||||||
const mode=document.createElement("select");mode.className="am-sel";
|
const mode=document.createElement("select");mode.className="am-sel";
|
||||||
[["default","imgQuotaDefault"],["custom","imgQuotaCustom"],["unlimited","imgUnlimited"]].forEach(([v,k])=>mode.add(new Option(T(k),v)));
|
const defaultLabel=type==="users"&&item.defaultBytes!=null
|
||||||
|
? T("imgQuotaDefaultOf").replace("{limit}",imageBytes(item.defaultBytes))
|
||||||
|
: T("imgQuotaDefault");
|
||||||
|
[[defaultLabel,"default"],[T("imgQuotaCustom"),"custom"],[T("imgUnlimited"),"unlimited"]]
|
||||||
|
.forEach(([label,v])=>mode.add(new Option(label,v)));
|
||||||
mode.value=item.overrideBytes==null?"default":item.overrideBytes===-1?"unlimited":"custom";
|
mode.value=item.overrideBytes==null?"default":item.overrideBytes===-1?"unlimited":"custom";
|
||||||
const amount=document.createElement("input");amount.className="am-inp";amount.type="number";amount.min="0";amount.max=String(10*1024);amount.step="10";
|
const amount=document.createElement("input");amount.className="am-inp";amount.type="number";amount.min="0";amount.max=String(10*1024);amount.step="10";
|
||||||
amount.value=item.overrideBytes!=null&&item.overrideBytes>=0?Math.round(item.overrideBytes/(1024*1024)):"";
|
amount.value=item.overrideBytes!=null&&item.overrideBytes>=0?Math.round(item.overrideBytes/(1024*1024)):"";
|
||||||
|
|
@ -368,18 +377,69 @@ function renderImageStoragePanel(panel){
|
||||||
try{await api("/images/admin/"+type+"/"+item.id+"/quota",{method:"PATCH",body:{limitBytes}});renderImageStoragePanel(panel)}
|
try{await api("/images/admin/"+type+"/"+item.id+"/quota",{method:"PATCH",body:{limitBytes}});renderImageStoragePanel(panel)}
|
||||||
catch(err){status.textContent=err.message}
|
catch(err){status.textContent=err.message}
|
||||||
};
|
};
|
||||||
row.append(info,mode,amount,save);return row;
|
wrap.append(mode,amount,save);
|
||||||
|
return wrap;
|
||||||
|
};
|
||||||
|
const usage=item=>T("imgQuotaUsage").replace("{used}",imageBytes(item.usedBytes)).replace("{limit}",fmt(item.limitBytes));
|
||||||
|
/* compacte gebruikersrij binnen de kaart: naam + verbruik, Bewerken klapt uit */
|
||||||
|
const userSub=u=>{
|
||||||
|
const row=document.createElement("div");row.className="am-row am-card-sub";
|
||||||
|
const nm=document.createElement("span");nm.className="am-name";nm.textContent=u.displayName;
|
||||||
|
const use=document.createElement("span");use.className="am-role";use.textContent=usage(u);
|
||||||
|
row.append(nm,use);
|
||||||
|
const tools=document.createElement("div");tools.className="am-row-tools";
|
||||||
|
tools.appendChild(editorTools(u,"users"));
|
||||||
|
const btn=document.createElement("button");btn.className="am-edit";btn.type="button";btn.textContent=T("amEdit");
|
||||||
|
const key="u:"+u.id;
|
||||||
|
const sync=()=>{const open=imageQuotaOpen.has(key);tools.style.display=open?"":"none";btn.classList.toggle("on",open)};
|
||||||
|
btn.addEventListener("click",()=>{imageQuotaOpen.has(key)?imageQuotaOpen.delete(key):imageQuotaOpen.add(key);sync()});
|
||||||
|
sync();
|
||||||
|
row.append(btn,tools);
|
||||||
|
return row;
|
||||||
};
|
};
|
||||||
(async()=>{
|
(async()=>{
|
||||||
try{
|
try{
|
||||||
const data=await api("/images/admin/quotas");host.innerHTML="";
|
const data=await api("/images/admin/quotas");host.innerHTML="";
|
||||||
const schoolTitle=document.createElement("div");schoolTitle.className="am-group";schoolTitle.textContent=T("imgSchools");
|
/* stap 1: kies een school (of Systeem) - geen eindeloze platte lijsten */
|
||||||
host.append(schoolTitle,...data.schools.map(s=>editor({...s,name:s.name},"schools")));
|
const pick=document.createElement("select");pick.className="am-sel img-quota-pick";
|
||||||
const userTitle=document.createElement("div");userTitle.className="am-group";userTitle.textContent=T("imgUsers");
|
pick.append(new Option(T("imgPickSchool"),""));
|
||||||
const filter=document.createElement("input");filter.className="am-inp";filter.type="search";filter.placeholder=T("imgSearchUsers");
|
data.schools.forEach(s=>pick.append(new Option(s.name,String(s.id))));
|
||||||
const users=document.createElement("div");users.className="img-quota-users";
|
pick.append(new Option(T("imgSystemScope"),"none"));
|
||||||
const draw=()=>{const term=filter.value.trim().toLowerCase();users.innerHTML="";data.users.filter(u=>(u.displayName+" "+u.username).toLowerCase().includes(term)).forEach(u=>users.append(editor(u,"users")))};
|
pick.value=imageQuotaSchool;
|
||||||
filter.oninput=draw;host.append(userTitle,filter,users);draw();
|
if(pick.value!==imageQuotaSchool) imageQuotaSchool=""; /* school verdwenen */
|
||||||
|
pick.onchange=()=>{imageQuotaSchool=pick.value;imageQuotaFilter="";if(pick.value)imageQuotaOpen.add("school:"+pick.value);renderImageStoragePanel(panel)};
|
||||||
|
host.append(pick);
|
||||||
|
if(!imageQuotaSchool){
|
||||||
|
const hint=document.createElement("div");hint.className="guestnote";hint.textContent=T("amPickSchoolHint");
|
||||||
|
host.append(hint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const system=imageQuotaSchool==="none";
|
||||||
|
const school=system?null:data.schools.find(s=>String(s.id)===imageQuotaSchool);
|
||||||
|
let users=data.users.filter(u=>system?u.schoolId==null:Number(u.schoolId)===Number(imageQuotaSchool));
|
||||||
|
/* zoeken binnen de gekozen school */
|
||||||
|
const filter=document.createElement("input");filter.className="am-inp";filter.type="search";
|
||||||
|
filter.placeholder=T("imgSearchUsers");filter.value=imageQuotaFilter;
|
||||||
|
filter.oninput=()=>{imageQuotaFilter=filter.value;draw()};
|
||||||
|
host.append(filter);
|
||||||
|
const cardHost=document.createElement("div");
|
||||||
|
host.append(cardHost);
|
||||||
|
const draw=()=>{
|
||||||
|
cardHost.innerHTML="";
|
||||||
|
const term=imageQuotaFilter.trim().toLowerCase();
|
||||||
|
const shown=users.filter(u=>(u.displayName+" "+u.username).toLowerCase().includes(term));
|
||||||
|
const card=amGroupCard({
|
||||||
|
icon:"🏫",
|
||||||
|
title:system?T("imgSystemScope"):school.name,
|
||||||
|
summary:(school?usage(school)+" · ":"")+shown.length+" "+T("imgUsers").toLowerCase(),
|
||||||
|
key:"school:"+imageQuotaSchool,
|
||||||
|
openSet:imageQuotaOpen, editSet:imageQuotaOpen,
|
||||||
|
edit:school?{key:"s:"+school.id,tools:editorTools(school,"schools")}:null,
|
||||||
|
subs:shown.map(userSub),
|
||||||
|
});
|
||||||
|
cardHost.append(card);
|
||||||
|
};
|
||||||
|
draw();
|
||||||
}catch(err){host.textContent="";status.textContent=err.message}
|
}catch(err){host.textContent="";status.textContent=err.message}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue