Voeg 28 herkenbare kleurplaten en creatieve niveaus toe (v0.4.66-beta) #4
5 changed files with 99 additions and 24 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.3.00
|
0.3.01-beta
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,8 @@
|
||||||
.tbtn:disabled{opacity:.55; cursor:default;}
|
.tbtn:disabled{opacity:.55; cursor:default;}
|
||||||
#btnSave{min-width:150px; text-align:center;}
|
#btnSave{min-width:150px; text-align:center;}
|
||||||
#btnSave.ok{background:var(--green); color:#fff;}
|
#btnSave.ok{background:var(--green); color:#fff;}
|
||||||
|
/* mislukte (auto)save: blijft rood staan tot een save wél lukt */
|
||||||
|
#toolbar #btnSave.err{background:var(--red); color:#fff;}
|
||||||
#btnUser.on{background:var(--accent-soft); color:var(--accent-ink);}
|
#btnUser.on{background:var(--accent-soft); color:var(--accent-ink);}
|
||||||
#langToggle{display:flex; background:var(--surface-2); border-radius:999px; padding:3px; gap:2px;}
|
#langToggle{display:flex; background:var(--surface-2); border-radius:999px; padding:3px; gap:2px;}
|
||||||
#langToggle button{
|
#langToggle button{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,18 @@
|
||||||
Widget framework
|
Widget framework
|
||||||
==========================================================*/
|
==========================================================*/
|
||||||
const board = document.getElementById("board");
|
const board = document.getElementById("board");
|
||||||
|
/* autosave-haakje: board.js meldt wijzigingen bij core.js, behalve tijdens restore */
|
||||||
|
const markBoardChange = ()=>{ if(!RESTORING) scheduleSave(); };
|
||||||
|
/* vangnet voor widget-INTERNE wijzigingen (notities typen, mindmap slepen,
|
||||||
|
schrijfblad, spelvoortgang): elke interactie in een widget-body plant een
|
||||||
|
(debounced) save - goedkoop, en dekt alle widgets zonder ze stuk voor stuk
|
||||||
|
te instrumenteren */
|
||||||
|
document.addEventListener("input", e=>{
|
||||||
|
if(e.target.closest && e.target.closest(".widget-body")) markBoardChange();
|
||||||
|
}, true);
|
||||||
|
document.addEventListener("pointerup", e=>{
|
||||||
|
if(e.target.closest && e.target.closest(".widget-body")) markBoardChange();
|
||||||
|
}, true);
|
||||||
const dock = document.getElementById("dock");
|
const dock = document.getElementById("dock");
|
||||||
let zTop = 10, widgetCount = 0;
|
let zTop = 10, widgetCount = 0;
|
||||||
const instances = new Set();
|
const instances = new Set();
|
||||||
|
|
@ -88,7 +100,7 @@ function makeWidget(def, saved){
|
||||||
nh = Math.max(def.minH || 180, Math.min(nh, maxH));
|
nh = Math.max(def.minH || 180, Math.min(nh, maxH));
|
||||||
win.style.width = nw+"px"; win.style.height = nh+"px";
|
win.style.width = nw+"px"; win.style.height = nh+"px";
|
||||||
};
|
};
|
||||||
const up = ()=>{ document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up); };
|
const up = ()=>{ document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up); markBoardChange(); };
|
||||||
document.addEventListener("pointermove",mv); document.addEventListener("pointerup",up);
|
document.addEventListener("pointermove",mv); document.addEventListener("pointerup",up);
|
||||||
});
|
});
|
||||||
win.appendChild(rs);
|
win.appendChild(rs);
|
||||||
|
|
@ -114,7 +126,7 @@ function makeWidget(def, saved){
|
||||||
win.style.zIndex = ++zTop;
|
win.style.zIndex = ++zTop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
minBtn.addEventListener("click", ()=>setMin(true));
|
minBtn.addEventListener("click", ()=>{ setMin(true); markBoardChange(); });
|
||||||
/* per-widget content zoom for low-vision pupils */
|
/* per-widget content zoom for low-vision pupils */
|
||||||
let wz = (saved && saved.wz) || 1;
|
let wz = (saved && saved.wz) || 1;
|
||||||
function applyWZ(){
|
function applyWZ(){
|
||||||
|
|
@ -127,6 +139,7 @@ function makeWidget(def, saved){
|
||||||
zBtn.addEventListener("click", ()=>{
|
zBtn.addEventListener("click", ()=>{
|
||||||
wz = wz===1 ? 1.25 : wz===1.25 ? 1.5 : 1;
|
wz = wz===1 ? 1.25 : wz===1.25 ? 1.5 : 1;
|
||||||
applyWZ();
|
applyWZ();
|
||||||
|
markBoardChange();
|
||||||
});
|
});
|
||||||
applyWZ();
|
applyWZ();
|
||||||
function setMax(m){
|
function setMax(m){
|
||||||
|
|
@ -148,7 +161,7 @@ function makeWidget(def, saved){
|
||||||
}
|
}
|
||||||
maxBtn.title = T(win.dataset.max==="1" ? "wUnfull" : "wFull");
|
maxBtn.title = T(win.dataset.max==="1" ? "wUnfull" : "wFull");
|
||||||
}
|
}
|
||||||
maxBtn.addEventListener("click", ()=>setMax(win.dataset.max!=="1"));
|
maxBtn.addEventListener("click", ()=>{ setMax(win.dataset.max!=="1"); markBoardChange(); });
|
||||||
head.addEventListener("dblclick", e=>{
|
head.addEventListener("dblclick", e=>{
|
||||||
if(e.target===close || e.target===minBtn || e.target===maxBtn) return;
|
if(e.target===close || e.target===minBtn || e.target===maxBtn) return;
|
||||||
setMin(true);
|
setMin(true);
|
||||||
|
|
@ -174,7 +187,7 @@ function makeWidget(def, saved){
|
||||||
win.style.left = Math.max(0, Math.min(board.clientWidth - 60, ev.clientX/zf - sx)) + "px";
|
win.style.left = Math.max(0, Math.min(board.clientWidth - 60, ev.clientX/zf - sx)) + "px";
|
||||||
win.style.top = Math.max(0, Math.min(board.clientHeight - 40, ev.clientY/zf - sy)) + "px";
|
win.style.top = Math.max(0, Math.min(board.clientHeight - 40, ev.clientY/zf - sy)) + "px";
|
||||||
};
|
};
|
||||||
const up = ()=>{ document.removeEventListener("pointermove",move); document.removeEventListener("pointerup",up); };
|
const up = ()=>{ document.removeEventListener("pointermove",move); document.removeEventListener("pointerup",up); markBoardChange(); };
|
||||||
document.addEventListener("pointermove",move); document.addEventListener("pointerup",up);
|
document.addEventListener("pointermove",move); document.addEventListener("pointerup",up);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -185,10 +198,12 @@ function makeWidget(def, saved){
|
||||||
const inst = { def, win, api };
|
const inst = { def, win, api };
|
||||||
instances.add(inst);
|
instances.add(inst);
|
||||||
close.addEventListener("click", ()=>{
|
close.addEventListener("click", ()=>{
|
||||||
|
markBoardChange();
|
||||||
if(pill){ pill.remove(); pill = null; }
|
if(pill){ pill.remove(); pill = null; }
|
||||||
instances.delete(inst); win.remove(); updateEmptyHint();
|
instances.delete(inst); win.remove(); updateEmptyHint();
|
||||||
});
|
});
|
||||||
updateEmptyHint();
|
updateEmptyHint();
|
||||||
|
markBoardChange();
|
||||||
return inst;
|
return inst;
|
||||||
}
|
}
|
||||||
function updateEmptyHint(){
|
function updateEmptyHint(){
|
||||||
|
|
@ -221,7 +236,11 @@ function serializeBoard(){
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/* tijdens het terugzetten van een bord geen autosaves plannen: het herstellen
|
||||||
|
zelf is geen wijziging (en zou anders direct na inloggen al een save triggeren) */
|
||||||
|
let RESTORING = false;
|
||||||
function restoreBoard(b){
|
function restoreBoard(b){
|
||||||
|
RESTORING = true;
|
||||||
[...instances].forEach(inst=>inst.win.remove());
|
[...instances].forEach(inst=>inst.win.remove());
|
||||||
instances.clear();
|
instances.clear();
|
||||||
dock.innerHTML = "";
|
dock.innerHTML = "";
|
||||||
|
|
@ -235,6 +254,7 @@ function restoreBoard(b){
|
||||||
board.querySelectorAll(".bimg").forEach(x=>x.remove());
|
board.querySelectorAll(".bimg").forEach(x=>x.remove());
|
||||||
(b.imgs||[]).forEach(im=>addBoardImage(im.src, im.x, im.y, im.w));
|
(b.imgs||[]).forEach(im=>addBoardImage(im.src, im.x, im.y, im.w));
|
||||||
updateEmptyHint();
|
updateEmptyHint();
|
||||||
|
RESTORING = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- board drawing layer (pen mode) ---------- */
|
/* ---------- board drawing layer (pen mode) ---------- */
|
||||||
|
|
@ -265,9 +285,11 @@ function setPen(on){
|
||||||
refreshPenbar();
|
refreshPenbar();
|
||||||
}
|
}
|
||||||
function setBoardBg(bg){
|
function setBoardBg(bg){
|
||||||
|
const changed = BOARD.bg !== bg;
|
||||||
BOARD.bg = bg;
|
BOARD.bg = bg;
|
||||||
board.className = (bg && bg!=="blank") ? "bg-"+bg : "";
|
board.className = (bg && bg!=="blank") ? "bg-"+bg : "";
|
||||||
refreshPenbar();
|
refreshPenbar();
|
||||||
|
if(changed) markBoardChange();
|
||||||
}
|
}
|
||||||
const PEN_TOOLS = [["hand","✋","toolHand"],["pen","✏️","toolPen"],["vulp","🖋️","toolFountain"],["mark","🖍️","toolMark"],["text","🔤","toolText"],["erase","🧽","eraser"]];
|
const PEN_TOOLS = [["hand","✋","toolHand"],["pen","✏️","toolPen"],["vulp","🖋️","toolFountain"],["mark","🖍️","toolMark"],["text","🔤","toolText"],["erase","🧽","eraser"]];
|
||||||
PEN_TOOLS.forEach(([id,icon])=>{
|
PEN_TOOLS.forEach(([id,icon])=>{
|
||||||
|
|
@ -646,6 +668,7 @@ bInk.addEventListener("pointermove", e=>{
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const bStop = ()=>{
|
const bStop = ()=>{
|
||||||
|
if(bDraw) markBoardChange();
|
||||||
bDraw = false;
|
bDraw = false;
|
||||||
bCtx.globalAlpha = 1;
|
bCtx.globalAlpha = 1;
|
||||||
if(DTOOLS.lens) DTOOLS.lens.lensDraw();
|
if(DTOOLS.lens) DTOOLS.lens.lensDraw();
|
||||||
|
|
@ -807,7 +830,7 @@ function addBoardImage(src, x, y, w){
|
||||||
d.appendChild(im);
|
d.appendChild(im);
|
||||||
const xb = document.createElement("button");
|
const xb = document.createElement("button");
|
||||||
xb.className = "dtool-x"; xb.textContent = "✕";
|
xb.className = "dtool-x"; xb.textContent = "✕";
|
||||||
xb.addEventListener("click", ()=>d.remove());
|
xb.addEventListener("click", ()=>{ d.remove(); markBoardChange(); });
|
||||||
d.appendChild(xb);
|
d.appendChild(xb);
|
||||||
const rs = document.createElement("div");
|
const rs = document.createElement("div");
|
||||||
rs.className = "bimg-rs";
|
rs.className = "bimg-rs";
|
||||||
|
|
@ -816,7 +839,7 @@ function addBoardImage(src, x, y, w){
|
||||||
const zf = VZ();
|
const zf = VZ();
|
||||||
const sw = d.offsetWidth, sx = e.clientX;
|
const sw = d.offsetWidth, sx = e.clientX;
|
||||||
const mv = ev=>{ d.style.width = Math.max(80, sw + (ev.clientX - sx)/zf)+"px"; };
|
const mv = ev=>{ d.style.width = Math.max(80, sw + (ev.clientX - sx)/zf)+"px"; };
|
||||||
const up = ()=>{ document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up); };
|
const up = ()=>{ document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up); markBoardChange(); };
|
||||||
document.addEventListener("pointermove",mv); document.addEventListener("pointerup",up);
|
document.addEventListener("pointermove",mv); document.addEventListener("pointerup",up);
|
||||||
});
|
});
|
||||||
d.appendChild(rs);
|
d.appendChild(rs);
|
||||||
|
|
@ -827,10 +850,11 @@ function addBoardImage(src, x, y, w){
|
||||||
const zf = VZ();
|
const zf = VZ();
|
||||||
const ox = e.clientX/zf - d.offsetLeft, oy = e.clientY/zf - d.offsetTop;
|
const ox = e.clientX/zf - d.offsetLeft, oy = e.clientY/zf - d.offsetTop;
|
||||||
const mv = ev=>{ d.style.left = (ev.clientX/zf-ox)+"px"; d.style.top = (ev.clientY/zf-oy)+"px"; };
|
const mv = ev=>{ d.style.left = (ev.clientX/zf-ox)+"px"; d.style.top = (ev.clientY/zf-oy)+"px"; };
|
||||||
const up = ()=>{ document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up); };
|
const up = ()=>{ document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up); markBoardChange(); };
|
||||||
document.addEventListener("pointermove",mv); document.addEventListener("pointerup",up);
|
document.addEventListener("pointermove",mv); document.addEventListener("pointerup",up);
|
||||||
});
|
});
|
||||||
board.appendChild(d);
|
board.appendChild(d);
|
||||||
|
markBoardChange();
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
document.addEventListener("langchange", refreshFab);
|
document.addEventListener("langchange", refreshFab);
|
||||||
|
|
@ -874,6 +898,7 @@ function loadCurrentSlot(){
|
||||||
}
|
}
|
||||||
function switchTo(f, b){
|
function switchTo(f, b){
|
||||||
stashCurrent();
|
stashCurrent();
|
||||||
|
markBoardChange();
|
||||||
BS.f = f; BS.b = b;
|
BS.f = f; BS.b = b;
|
||||||
loadCurrentSlot();
|
loadCurrentSlot();
|
||||||
}
|
}
|
||||||
|
|
@ -982,6 +1007,7 @@ function renderStrip(){
|
||||||
if(!fo.boards.length) fo.boards.push(newBoardEntry(T("boardName")+" 1"));
|
if(!fo.boards.length) fo.boards.push(newBoardEntry(T("boardName")+" 1"));
|
||||||
if(isCur){ BS.f = it.fi; BS.b = 0; loadCurrentSlot(); }
|
if(isCur){ BS.f = it.fi; BS.b = 0; loadCurrentSlot(); }
|
||||||
else if(it.fi===BS.f && it.bi < BS.b) BS.b--;
|
else if(it.fi===BS.f && it.bi < BS.b) BS.b--;
|
||||||
|
markBoardChange();
|
||||||
renderStrip();
|
renderStrip();
|
||||||
});
|
});
|
||||||
row.appendChild(x);
|
row.appendChild(x);
|
||||||
|
|
@ -1026,6 +1052,7 @@ function renderBoardsPanel(){
|
||||||
del.addEventListener("click", ()=>{
|
del.addEventListener("click", ()=>{
|
||||||
if(fi === BS.f){ BS.folders.splice(fi,1); BS.f = 0; BS.b = 0; loadCurrentSlot(); }
|
if(fi === BS.f){ BS.folders.splice(fi,1); BS.f = 0; BS.b = 0; loadCurrentSlot(); }
|
||||||
else{ BS.folders.splice(fi,1); if(BS.f > fi) BS.f--; }
|
else{ BS.folders.splice(fi,1); if(BS.f > fi) BS.f--; }
|
||||||
|
markBoardChange();
|
||||||
renderBoardsPanel();
|
renderBoardsPanel();
|
||||||
});
|
});
|
||||||
fr.appendChild(del);
|
fr.appendChild(del);
|
||||||
|
|
@ -1055,6 +1082,7 @@ function renderBoardsPanel(){
|
||||||
const commit = ()=>{
|
const commit = ()=>{
|
||||||
if(done) return; done = true;
|
if(done) return; done = true;
|
||||||
bo.name = inp.value.trim() || bo.name;
|
bo.name = inp.value.trim() || bo.name;
|
||||||
|
markBoardChange();
|
||||||
renderBoardsPanel(); updateBoardsUI();
|
renderBoardsPanel(); updateBoardsUI();
|
||||||
};
|
};
|
||||||
inp.addEventListener("keydown", ev=>{ ev.stopPropagation(); if(ev.key==="Enter") commit(); });
|
inp.addEventListener("keydown", ev=>{ ev.stopPropagation(); if(ev.key==="Enter") commit(); });
|
||||||
|
|
@ -1071,6 +1099,7 @@ function renderBoardsPanel(){
|
||||||
if(!fo.boards.length) fo.boards.push(newBoardEntry(T("boardName")+" 1"));
|
if(!fo.boards.length) fo.boards.push(newBoardEntry(T("boardName")+" 1"));
|
||||||
if(wasCur){ BS.b = 0; loadCurrentSlot(); }
|
if(wasCur){ BS.b = 0; loadCurrentSlot(); }
|
||||||
else if(fi===BS.f && bi < BS.b) BS.b--;
|
else if(fi===BS.f && bi < BS.b) BS.b--;
|
||||||
|
markBoardChange();
|
||||||
renderBoardsPanel();
|
renderBoardsPanel();
|
||||||
});
|
});
|
||||||
chip.appendChild(x);
|
chip.appendChild(x);
|
||||||
|
|
@ -1087,6 +1116,7 @@ function renderBoardsPanel(){
|
||||||
const name = inp.value.trim() || `${T("folderName")} ${BS.folders.length+1}`;
|
const name = inp.value.trim() || `${T("folderName")} ${BS.folders.length+1}`;
|
||||||
BS.folders.push({ name, boards:[newBoardEntry(T("boardName")+" 1")] });
|
BS.folders.push({ name, boards:[newBoardEntry(T("boardName")+" 1")] });
|
||||||
inp.value = "";
|
inp.value = "";
|
||||||
|
markBoardChange();
|
||||||
renderBoardsPanel();
|
renderBoardsPanel();
|
||||||
};
|
};
|
||||||
inp.addEventListener("keydown", e=>{ e.stopPropagation(); if(e.key==="Enter") addFolder(); });
|
inp.addEventListener("keydown", e=>{ e.stopPropagation(); if(e.key==="Enter") addFolder(); });
|
||||||
|
|
|
||||||
|
|
@ -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.00";
|
const VERSION = "0.3.01-beta";
|
||||||
(function(){
|
(function(){
|
||||||
const tag = document.getElementById("verTag");
|
const tag = document.getElementById("verTag");
|
||||||
tag.textContent = "v"+VERSION;
|
tag.textContent = "v"+VERSION;
|
||||||
|
|
@ -166,7 +166,8 @@ const I18N = {
|
||||||
errName:"Naam: 2–20 letters of cijfers.",
|
errName:"Naam: 2–20 letters of cijfers.",
|
||||||
errPass:"Wachtwoord: minimaal 4 tekens.",
|
errPass:"Wachtwoord: minimaal 4 tekens.",
|
||||||
errCrypto:"Inloggen werkt niet in deze browser.",
|
errCrypto:"Inloggen werkt niet in deze browser.",
|
||||||
hello:"Hoi", boardSaved:"Opgeslagen ✓", loginFirst:"Eerst inloggen"
|
hello:"Hoi", boardSaved:"Opgeslagen ✓", loginFirst:"Eerst inloggen",
|
||||||
|
saving:"Opslaan…", saveFailed:"⚠ Niet opgeslagen"
|
||||||
},
|
},
|
||||||
en:{
|
en:{
|
||||||
addWidget:"Widget", chooseWidget:"Choose a widget", saveBoard:"Save board",
|
addWidget:"Widget", chooseWidget:"Choose a widget", saveBoard:"Save board",
|
||||||
|
|
@ -305,7 +306,8 @@ const I18N = {
|
||||||
errName:"Name: 2–20 letters or digits.",
|
errName:"Name: 2–20 letters or digits.",
|
||||||
errPass:"Password: at least 4 characters.",
|
errPass:"Password: at least 4 characters.",
|
||||||
errCrypto:"Login does not work in this browser.",
|
errCrypto:"Login does not work in this browser.",
|
||||||
hello:"Hi", boardSaved:"Saved ✓", loginFirst:"Log in first"
|
hello:"Hi", boardSaved:"Saved ✓", loginFirst:"Log in first",
|
||||||
|
saving:"Saving…", saveFailed:"⚠ Not saved"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let LANG = "nl";
|
let LANG = "nl";
|
||||||
|
|
@ -363,18 +365,59 @@ async function api(path, opts={}){
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* alles van de gebruiker (borden, thema's, eigen woorden) staat op de server */
|
/* =========================================================
|
||||||
|
Opslaan - alles van de gebruiker staat op de server.
|
||||||
|
Autosave: elke wijziging plant (debounced) een save; de status
|
||||||
|
is altijd zichtbaar op de opslaan-knop en fouten worden NIET
|
||||||
|
meer ingeslikt (die stilte verborg de 1MB-limiet-bug).
|
||||||
|
==========================================================*/
|
||||||
let saveTimer = null;
|
let saveTimer = null;
|
||||||
|
let lastSaveError = null;
|
||||||
|
function canPersist(){
|
||||||
|
return !!currentUser && currentUser.role !== "pupil";
|
||||||
|
}
|
||||||
function persistUser(){
|
function persistUser(){
|
||||||
if(!currentUser) return Promise.resolve();
|
if(!canPersist()) return Promise.resolve(true);
|
||||||
return api("/me/data", { method:"PUT", body:{
|
return api("/me/data", { method:"PUT", body:{
|
||||||
themes: THEMES, generalExtra: GENERAL_EXTRA, boards: BS
|
themes: THEMES, generalExtra: GENERAL_EXTRA, boards: BS
|
||||||
}}).catch(()=>{});
|
}}).then(()=>{ lastSaveError = null; return true; })
|
||||||
|
.catch(e=>{ lastSaveError = e.message; return false; });
|
||||||
|
}
|
||||||
|
/* status op de opslaan-knop: idle / saving / saved / error */
|
||||||
|
function setSaveStatus(state){
|
||||||
|
const btn = document.getElementById("btnSave");
|
||||||
|
const lbl = btn.querySelector("span");
|
||||||
|
btn.classList.remove("ok","err");
|
||||||
|
btn.title = "";
|
||||||
|
if(state==="saving"){ lbl.textContent = T("saving"); }
|
||||||
|
else if(state==="saved"){ lbl.textContent = T("boardSaved"); btn.classList.add("ok"); }
|
||||||
|
else if(state==="error"){
|
||||||
|
lbl.textContent = T("saveFailed");
|
||||||
|
btn.classList.add("err");
|
||||||
|
btn.title = lastSaveError || "";
|
||||||
|
}
|
||||||
|
else lbl.textContent = T("saveBoard");
|
||||||
|
}
|
||||||
|
let saveStatusTimer = null;
|
||||||
|
async function doSave(){
|
||||||
|
if(!canPersist()) return false;
|
||||||
|
stashCurrent();
|
||||||
|
setSaveStatus("saving");
|
||||||
|
const ok = await persistUser();
|
||||||
|
clearTimeout(saveStatusTimer);
|
||||||
|
setSaveStatus(ok ? "saved" : "error");
|
||||||
|
/* een fout blijft staan tot een geslaagde save; succes ebt weg */
|
||||||
|
if(ok) saveStatusTimer = setTimeout(()=>setSaveStatus("idle"), 2000);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
/* autosave: door alles aan te roepen wat het bord of de woorden wijzigt */
|
||||||
|
function scheduleSave(){
|
||||||
|
if(!canPersist()) return;
|
||||||
|
clearTimeout(saveTimer);
|
||||||
|
saveTimer = setTimeout(doSave, 2500);
|
||||||
}
|
}
|
||||||
function persistWords(){
|
function persistWords(){
|
||||||
if(!currentUser) return;
|
scheduleSave();
|
||||||
clearTimeout(saveTimer);
|
|
||||||
saveTimer = setTimeout(()=>{ stashCurrent(); persistUser(); }, 800);
|
|
||||||
}
|
}
|
||||||
function generalExtraFromData(data){
|
function generalExtraFromData(data){
|
||||||
return (data && data.generalExtra) ? data.generalExtra : {nl:[], en:[]};
|
return (data && data.generalExtra) ? data.generalExtra : {nl:[], en:[]};
|
||||||
|
|
@ -569,8 +612,7 @@ document.getElementById("btnOwnPw").addEventListener("click", async ()=>{
|
||||||
|
|
||||||
/* save board */
|
/* save board */
|
||||||
document.getElementById("btnSave").addEventListener("click", async ()=>{
|
document.getElementById("btnSave").addEventListener("click", async ()=>{
|
||||||
const btn = document.getElementById("btnSave");
|
const lbl = document.getElementById("btnSave").querySelector("span");
|
||||||
const lbl = btn.querySelector("span");
|
|
||||||
if(!currentUser){
|
if(!currentUser){
|
||||||
lbl.textContent = T("loginFirst");
|
lbl.textContent = T("loginFirst");
|
||||||
setTimeout(()=>{ lbl.textContent = T("saveBoard"); }, 2200);
|
setTimeout(()=>{ lbl.textContent = T("saveBoard"); }, 2200);
|
||||||
|
|
@ -578,11 +620,8 @@ document.getElementById("btnSave").addEventListener("click", async ()=>{
|
||||||
loginWrap.classList.add("open");
|
loginWrap.classList.add("open");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stashCurrent();
|
clearTimeout(saveTimer); /* expliciet opslaan gaat vóór de geplande autosave */
|
||||||
await persistUser();
|
await doSave();
|
||||||
lbl.textContent = T("boardSaved");
|
|
||||||
btn.classList.add("ok");
|
|
||||||
setTimeout(()=>{ lbl.textContent = T("saveBoard"); btn.classList.remove("ok"); }, 1800);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ const HOST = process.env.HOST ?? '0.0.0.0';
|
||||||
const app = Fastify({
|
const app = Fastify({
|
||||||
logger: true,
|
logger: true,
|
||||||
trustProxy: true, // achter nginx reverse proxy
|
trustProxy: true, // achter nginx reverse proxy
|
||||||
|
// borden bevatten tekeningen/foto's als base64 in de JSON; de Fastify-standaard
|
||||||
|
// van 1 MB weigerde die stilletjes (413) waardoor opslaan leek te lukken maar
|
||||||
|
// niets bewaarde. Ruime limiet + eerlijke foutmelding client-side (core.js).
|
||||||
|
bodyLimit: 25 * 1024 * 1024,
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Database pool -----------------------------------------------------------
|
// --- Database pool -----------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue