Verbeter grafische kwaliteit van kleurplaten (v0.4.67-beta) #5

Open
bes-r wants to merge 208 commits from bes-r/coloring-quality-v0.4.67 into main AGit
4 changed files with 128 additions and 35 deletions
Showing only changes of commit e327049f1b - Show all commits

View file

@ -1 +1 @@
0.3.22-beta
0.3.23-beta

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.3.22-beta";
const VERSION = "0.3.23-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;
@ -53,7 +53,8 @@ const I18N = {
myWords:"Mijn woorden", manage:"Thema's en woorden beheren", addWord:" Toevoegen",
pickPic:"Kies een plaatje",
newTheme:"Nieuw thema", themeNamePh:"naam van het thema…", delTheme:"Thema verwijderen",
themeFolder:"Thema in een map zetten", themeFolderPh:"mapnaam (leeg = geen map)…",
themeFolder:"Thema's en mappen",
exFixedFolder:"Deze map wordt automatisch beheerd.",
numWords:"Woorden per spel", all:"Alle", noThemes:"— nog geen thema's —",
muteAll:"Alle geluiden aan/uit", sndWidget:"Geluid van deze widget aan/uit",
catTaal:"Taal", catRekenen:"Rekenen", catClass:"Feest & klas", catTools:"Hulpmiddelen", catMedia:"Media",
@ -225,7 +226,8 @@ const I18N = {
myWords:"My words", manage:"Manage themes and words", addWord:" Add",
pickPic:"Choose a picture",
newTheme:"New theme", themeNamePh:"name of the theme…", delTheme:"Delete theme",
themeFolder:"Put theme in a folder", themeFolderPh:"folder name (empty = no folder)…",
themeFolder:"Themes and folders",
exFixedFolder:"This folder is managed automatically.",
numWords:"Words per game", all:"All", noThemes:"— no themes yet —",
muteAll:"All sounds on/off", sndWidget:"This widget's sound on/off",
catTaal:"Language", catRekenen:"Maths", catClass:"Celebration & class", catTools:"Tools", catMedia:"Media",

View file

@ -244,7 +244,7 @@
t.appendChild(c);
t.addEventListener("click", ()=>{ SEL = {type:"folder", key:f.name}; markSel(t); });
t.addEventListener("dblclick", ()=>{ PATH = [...PATH, f.name]; SEL = null; render(); msg(""); });
makeDraggable(t, {type:"folder", key:f.name});
makeDraggable(t, {type:"folder", key:f.name, movable: f.fixed !== true});
gridEl.appendChild(t);
});
items.forEach(it=>{

View file

@ -39,8 +39,7 @@ function mountLetterGame(root, initState, opts){
<select class="ed-theme-sel"></select>
<button class="tbtn ed-theme-add"></button>
<input type="text" class="ed-theme-name" maxlength="16" style="display:none;">
<button class="ed-theme-mv">📁</button>
<input type="text" class="ed-theme-folder" maxlength="16" style="display:none;">
<button class="ed-theme-mv">🗂</button>
<button class="ed-theme-del">🗑</button>
</div>
<div class="lg-ed-form">
@ -303,7 +302,7 @@ function mountLetterGame(root, initState, opts){
edPGrid = el(".ed-pgrid"), edStatus = el(".ed-status"),
edThemeSel = el(".ed-theme-sel"), edThemeAdd = el(".ed-theme-add"),
edThemeName = el(".ed-theme-name"), edThemeDel = el(".ed-theme-del"),
edThemeMv = el(".ed-theme-mv"), edThemeFolder = el(".ed-theme-folder");
edThemeMv = el(".ed-theme-mv");
let edPic = "", edTab = "emoji", edCat = 0, edTheme = "g";
/* ---- word target: the general card or one of the themes ---- */
@ -340,12 +339,9 @@ function mountLetterGame(root, initState, opts){
edThemeDel.title = T("delTheme");
edThemeMv.title = T("themeFolder");
edThemeName.placeholder = T("themeNamePh");
edThemeFolder.placeholder = T("themeFolderPh");
edThemeMv.style.display = edTheme === "g" ? "none" : "";
}
edThemeSel.addEventListener("change", ()=>{
edTheme = edThemeSel.value==="g" ? "g" : +edThemeSel.value;
edThemeMv.style.display = edTheme === "g" ? "none" : "";
renderEdList();
});
edThemeAdd.addEventListener("click", ()=>{
@ -381,34 +377,129 @@ function mountLetterGame(root, initState, opts){
renderThemeSel(); renderEdList();
document.dispatchEvent(new CustomEvent("wordschange"));
});
/* thema in een map zetten (of eruit halen met een leeg veld): 📁 toont een
inline invoer, voorgevuld met de huidige map van het gekozen thema */
edThemeMv.addEventListener("click", ()=>{
if(edTheme==="g") return;
edThemeFolder.style.display = "block";
edThemeFolder.value = THEMES[S.lang][edTheme].folder || "";
edThemeFolder.focus();
});
let folderDone = false;
function commitFolder(){
if(folderDone) return; folderDone = true;
if(edTheme !== "g" && THEMES[S.lang][edTheme]){
const f = edThemeFolder.value.trim();
const th = THEMES[S.lang][edTheme];
if(f) th.folder = f; else delete th.folder;
/* 🗂 opent de mappenverkenner voor thema's: zelfde Windows-stijl verkenner
als voor de borden (explorer.js), met de thema's van de actieve taal.
Nesting zit in het folder-veld als pad met "/"-scheiding. Ankerthema's
(id "an-...") staan vast in de map "⚓" - anchor.js zet die toch terug. */
function themesExplorerAdapter(){
const split = n => (n||"").split("/").map(s=>s.trim()).filter(Boolean);
const join = p => p.join("/");
const themes = ()=>THEMES[S.lang];
const isAnchor = th => typeof th.id === "string" && th.id.startsWith("an-");
/* mappen zijn afgeleid van themavelden; nieuwe lege mappen bestaan
sessie-lokaal tot er een thema in wordt gezet */
const pending = new Set();
const changed = ()=>{
persistWords();
renderThemeSel();
document.dispatchEvent(new CustomEvent("wordschange"));
}
edThemeFolder.style.display = "none";
setTimeout(()=>{ folderDone = false; }, 100);
};
return {
title: ()=>T("themeFolder"),
allowRootItems: true,
newItemLabel: ()=>T("newTheme"),
list(path){
const p = join(path);
const folderMap = new Map();
const items = [];
const seen = new Set(pending);
themes().forEach((th, i)=>{
const j = join(split(th.folder));
if(j === p){
items.push({
key: i, name: `${th.name} (${th.words.length})`,
icon: isAnchor(th) ? "⚓" : "★",
movable: !isAnchor(th), deletable: !isAnchor(th),
active: edTheme === i,
});
}else if(!p || j.startsWith(p + "/")){
seen.add(j);
}
});
seen.forEach(j=>{
if(!(j === p || (!p && j) || j.startsWith(p + "/"))) return;
if(j === p) return;
const seg = (p ? j.slice(p.length + 1) : j).split("/")[0];
if(!seg) return;
const under = themes().filter(t=>{ const tj = join(split(t.folder)); const full = p ? p+"/"+seg : seg; return tj === full || tj.startsWith(full + "/"); }).length;
folderMap.set(seg, under);
});
const folders = [...folderMap].map(([name, count])=>({name, count, fixed: name === "⚓" && !p}))
.sort((a,b)=>a.name.localeCompare(b.name));
return { folders, items };
},
createFolder(path, name){
const full = join([...path, name]);
if(this.list(path).folders.some(f=>f.name===name)) return T("exExists");
pending.add(full);
return null;
},
renameFolder(path, oldName, newName){
return this.moveFolder([...path, oldName], path, newName);
},
moveFolder(srcPath, targetPath, newName){
const from = join(srcPath);
if(from === "⚓") return T("exFixedFolder");
const to = join([...targetPath, newName || srcPath[srcPath.length-1]]);
if(from === to) return null;
if(themes().some(t=>{ const j = join(split(t.folder)); return j === to || j.startsWith(to + "/"); }) || pending.has(to)) return T("exExists");
themes().forEach(t=>{
const j = join(split(t.folder));
if(j === from || j.startsWith(from + "/")) t.folder = to + j.slice(from.length);
});
[...pending].forEach(j=>{
if(j === from || j.startsWith(from + "/")){ pending.delete(j); pending.add(to + j.slice(from.length)); }
});
changed();
return null;
},
deleteFolder(path, name){
const full = join([...path, name]);
if(full === "⚓") return T("exFixedFolder");
if(themes().some(t=>{ const j = join(split(t.folder)); return j === full || j.startsWith(full + "/"); })) return T("exFolderNotEmpty");
[...pending].forEach(j=>{ if(j === full || j.startsWith(full + "/")) pending.delete(j); });
return null;
},
createItem(path){
const nm = (S.lang==="nl" ? "Thema " : "Theme ") + (themes().length + 1);
const th = { name: nm, words: [] };
if(path.length) th.folder = join(path);
themes().push(th);
pending.delete(join(path));
changed();
return null;
},
renameItem(i, name){
const th = themes()[i];
if(!th || isAnchor(th)) return isAnchor(th) ? T("exFixedFolder") : null;
th.name = name;
changed();
return null;
},
moveItem(i, targetPath){
const th = themes()[i];
if(!th || isAnchor(th)) return th ? T("exFixedFolder") : null;
if(targetPath.length) th.folder = join(targetPath); else delete th.folder;
pending.delete(join(targetPath));
changed();
return null;
},
deleteItem(i){
const th = themes()[i];
if(!th || isAnchor(th)) return th ? T("exFixedFolder") : null;
themes().splice(i, 1);
if(edTheme === i) edTheme = "g";
else if(edTheme !== "g" && edTheme > i) edTheme--;
changed();
return null;
},
openItem(i){
if(themes()[i]) edTheme = i;
},
};
}
edThemeFolder.addEventListener("keydown", ev=>{
ev.stopPropagation();
if(ev.key==="Enter") commitFolder();
if(ev.key==="Escape"){ folderDone=true; edThemeFolder.style.display="none"; setTimeout(()=>{folderDone=false;},100); }
edThemeMv.addEventListener("click", ()=>{
openExplorer(themesExplorerAdapter(), ()=>{ renderThemeSel(); renderEdList(); });
});
edThemeFolder.addEventListener("blur", commitFolder);
function selectPic(p, btn){
edPic = p; setPic(edPrev, p);