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);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 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;
|
||||
}
|
||||
|
||||
/* ---- 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) ------------
|
||||
Elke sectie meldt zichzelf aan met rol-zichtbaarheid, een activate() en
|
||||
zoekgegevens; de schil bouwt daar navigatie + zoeken uit. Per rol alleen
|
||||
|
|
|
|||
|
|
@ -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.3.87-beta";
|
||||
const VERSION = "0.3.88-beta";
|
||||
(function(){
|
||||
const tag = document.getElementById("verTag");
|
||||
tag.textContent = "v"+VERSION;
|
||||
|
|
@ -190,6 +190,8 @@ const I18N = {
|
|||
imgLoading:"Afbeeldingen laden…", imgSaved:"Afbeelding opgeslagen ✓", imgDeleted:"Afbeelding verwijderd ✓",
|
||||
imgThemeAdded:"Thema toegevoegd ✓", imgStorageAdmin:"Opslaglimieten afbeeldingen",
|
||||
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",
|
||||
imgUsers:"Gebruikers", imgSearchUsers:"Zoek gebruiker…",
|
||||
cats:["Dieren","Eten","Natuur","Vervoer","Spullen","Lichaam"],
|
||||
|
|
@ -482,6 +484,8 @@ const I18N = {
|
|||
imgLoading:"Loading images…", imgSaved:"Image saved ✓", imgDeleted:"Image deleted ✓",
|
||||
imgThemeAdded:"Theme added ✓", imgStorageAdmin:"Image storage limits",
|
||||
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",
|
||||
imgUsers:"Users", imgSearchUsers:"Search user…",
|
||||
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
|
||||
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){
|
||||
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(heading,status);
|
||||
const status=document.createElement("div");status.className="am-msg";panel.append(status);
|
||||
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 editor=(item,type)=>{
|
||||
const row=document.createElement("div");row.className="am-row img-quota-row";
|
||||
const info=document.createElement("div");info.className="am-name";info.textContent=item.name||item.displayName;
|
||||
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);
|
||||
/* het bewerkblok (limiet-keuze + opslaan) achter de Bewerken-knop per kaart/rij */
|
||||
const editorTools=(item,type)=>{
|
||||
const wrap=document.createElement("div");wrap.className="am-inline";
|
||||
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";
|
||||
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)):"";
|
||||
|
|
@ -368,18 +377,69 @@ function renderImageStoragePanel(panel){
|
|||
try{await api("/images/admin/"+type+"/"+item.id+"/quota",{method:"PATCH",body:{limitBytes}});renderImageStoragePanel(panel)}
|
||||
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()=>{
|
||||
try{
|
||||
const data=await api("/images/admin/quotas");host.innerHTML="";
|
||||
const schoolTitle=document.createElement("div");schoolTitle.className="am-group";schoolTitle.textContent=T("imgSchools");
|
||||
host.append(schoolTitle,...data.schools.map(s=>editor({...s,name:s.name},"schools")));
|
||||
const userTitle=document.createElement("div");userTitle.className="am-group";userTitle.textContent=T("imgUsers");
|
||||
const filter=document.createElement("input");filter.className="am-inp";filter.type="search";filter.placeholder=T("imgSearchUsers");
|
||||
const users=document.createElement("div");users.className="img-quota-users";
|
||||
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")))};
|
||||
filter.oninput=draw;host.append(userTitle,filter,users);draw();
|
||||
/* stap 1: kies een school (of Systeem) - geen eindeloze platte lijsten */
|
||||
const pick=document.createElement("select");pick.className="am-sel img-quota-pick";
|
||||
pick.append(new Option(T("imgPickSchool"),""));
|
||||
data.schools.forEach(s=>pick.append(new Option(s.name,String(s.id))));
|
||||
pick.append(new Option(T("imgSystemScope"),"none"));
|
||||
pick.value=imageQuotaSchool;
|
||||
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}
|
||||
})();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue