Improve folder tree usability

This commit is contained in:
Mikei386
2026-06-21 16:20:10 +02:00
parent 2b6b629db9
commit 7dd0685a5a
8 changed files with 40 additions and 17 deletions
+15 -2
View File
@@ -510,11 +510,15 @@
function renderSourceBrowser() {
const host=document.getElementById('bu-source-browser');
if(!host) return;
const windowX=window.scrollX, windowY=window.scrollY;
const previousScroll=host.querySelector('.bu-tree')?.scrollTop || 0;
const chosen=state.backupSelections.length?state.backupSelections.map(path=>`<span class="bu-selection">${esc(path)}</span>`).join(''):'<span class="bu-muted">Keine Ordner ausgewählt.</span>';
const tree=state.sourceTreeRoots.map(item=>sourceTreeNode(item,0)).join('');
host.innerHTML=`<section class="bu-browser-card"><div class="bu-toolbar"><div><h3>Backup-Quellen auswählen</h3><span class="bu-muted">Ordner mit dem Dreieck aufklappen. Unterordner werden erst bei Bedarf geladen.</span></div><span class="bu-count">Ausgewählt: ${state.backupSelections.length}</span></div><div class="bu-selection-list">${chosen}</div><div class="bu-tree" role="tree">${tree||'<div class="bu-empty">Keine Ordner verfügbar.</div>'}</div></section>`;
const treeElement=host.querySelector('.bu-tree'); if(treeElement) treeElement.scrollTop=previousScroll;
host.querySelectorAll('.bu-source-select[data-partial="true"]').forEach(box=>{box.indeterminate=true;box.setAttribute('aria-checked','mixed');});
enhanceTooltips(host);
window.scrollTo(windowX, windowY);
}
async function toggleSourceTree(path, forceOpen=false) {
@@ -558,9 +562,13 @@
function renderDirectoryTree(key) {
const tree=state.directoryTrees[key]; const host=document.getElementById(`bu-dir-tree-${key}`);
if(!tree||!host) return;
const windowX=window.scrollX, windowY=window.scrollY;
const previousScroll=host.querySelector('.bu-tree')?.scrollTop || 0;
const html=tree.roots.map(item=>directoryTreeNode(key,item,0)).join('');
host.innerHTML=`<section class="bu-browser-card"><div class="bu-toolbar"><div><h3>${esc(tree.title)}</h3><span class="bu-muted">Ordner mit dem Dreieck aufklappen und den gewünschten Zielordner auswählen.</span></div></div><div class="bu-tree" role="tree">${html||'<div class="bu-empty">Keine Ordner verfügbar.</div>'}</div></section>`;
const treeElement=host.querySelector('.bu-tree'); if(treeElement) treeElement.scrollTop=previousScroll;
enhanceTooltips(host);
window.scrollTo(windowX, windowY);
}
async function toggleDirectoryTree(key, path, forceOpen=false) {
@@ -738,6 +746,11 @@
notify('Konfiguration gespeichert');
return state.config;
}
function renderPreservingScroll() {
const windowX=window.scrollX, windowY=window.scrollY;
render();
window.scrollTo(windowX, windowY);
}
const lines = (value) => value.split('\n').map(v=>v.trim()).filter(Boolean);
const runTime = run => new Date(run.createdAt || run.startedAt || run.finishedAt || 0).getTime() || 0;
const snapshotTime = snapshot => new Date(snapshot.time || 0).getTime() || 0;
@@ -773,7 +786,7 @@
if (name === 'toggle-dir-tree') { return toggleDirectoryTree(a, decodeURIComponent(b)); }
if (name === 'retry-dir-tree') { const path=decodeURIComponent(b); const tree=state.directoryTrees[a]; if(tree) { delete tree.children[path]; tree.errors[path]=''; } return toggleDirectoryTree(a,path,true); }
if (name === 'select-dir-tree') { selectDirectoryTreePath(a, decodeURIComponent(b)); return; }
if (name === 'toggle-snapshot-tree') { const path=decodeURIComponent(a); state.snapshotTreeExpanded[path]=!state.snapshotTreeExpanded[path]; return render(); }
if (name === 'toggle-snapshot-tree') { const path=decodeURIComponent(a); state.snapshotTreeExpanded[path]=!state.snapshotTreeExpanded[path]; return renderPreservingScroll(); }
if (name === 'restore-selected') { if(!state.restoreIncludes.length) throw new Error('Wähle mindestens eine Datei oder einen Ordner aus.'); state.view='restore'; document.querySelectorAll('.bu-tabs button').forEach(x=>x.classList.toggle('active',x.dataset.view==='restore')); return render(); }
if (name === 'delete-job') { if(confirm('Diesen Job löschen?')) { state.config.jobs=state.config.jobs.filter(j=>j.id!==a); await saveConfig(); render(); } return; }
if (name === 'duplicate-job') { const source=state.config.jobs.find(j=>j.id===a); const copy=structuredClone(source); copy.id=id('job'); copy.name += ' Kopie'; copy.enabled=false; state.config.jobs.push(copy); await saveConfig(); return render(); }
@@ -879,7 +892,7 @@
if (e.target.classList.contains('bu-file-select')) {
const path=e.target.value; const prefix=path.replace(/\/$/,'')+'/';
state.restoreIncludes = e.target.checked ? normalizeSourceSelections([...state.restoreIncludes.filter(selected=>!selected.startsWith(prefix)),path]) : state.restoreIncludes.filter(selected=>selected!==path);
render();
renderPreservingScroll();
}
if (e.target.classList.contains('bu-workload-select')) {
state.workloadSelections = e.target.checked ? [...new Set([...state.workloadSelections,e.target.value])] : state.workloadSelections.filter(id=>id!==e.target.value);