Improve folder tree usability
This commit is contained in:
@@ -84,18 +84,20 @@
|
||||
.bu-source-partial .bu-source-select { accent-color: var(--bu-accent); }
|
||||
.bu-partial-label { border: 1px solid var(--bu-accent); border-radius: 999px; color: var(--bu-text); display: inline-block; font-size: 11px; font-weight: 700; margin-left: 8px; padding: 2px 7px; vertical-align: middle; }
|
||||
.bu-inherited-label { border: 1px solid var(--bu-good); border-radius: 999px; color: var(--bu-good); display: inline-block; font-size: 11px; font-weight: 700; margin-left: 8px; padding: 2px 7px; vertical-align: middle; }
|
||||
.bu-tree { border: 1px solid var(--bu-border); border-radius: 6px; max-height: 520px; overflow: auto; padding: 5px; }
|
||||
.bu-tree { background: var(--bu-input); border: 1px solid var(--bu-border); border-radius: 6px; max-height: 520px; overflow: auto; padding: 6px; }
|
||||
.bu-tree-node { min-width: 0; }
|
||||
.bu-tree-row { align-items: center; border-radius: 5px; display: grid; gap: 6px; grid-template-columns: 28px 24px minmax(0, 1fr); margin-left: calc(var(--bu-tree-depth) * 22px); min-height: 42px; padding: 3px 7px; }
|
||||
.bu-dir-tree-row { grid-template-columns: 28px minmax(0, 1fr) auto; }
|
||||
.bu-tree-node + .bu-tree-node { border-top: 1px solid color-mix(in srgb, var(--bu-border) 45%, transparent); }
|
||||
.bu-tree-row { align-items: center; border-radius: 6px; display: grid; gap: 9px; grid-template-columns: 34px 24px minmax(0, 1fr); margin-left: calc(var(--bu-tree-depth) * 24px); min-height: 48px; padding: 5px 8px; }
|
||||
.bu-dir-tree-row { grid-template-columns: 34px minmax(0, 1fr) auto; }
|
||||
.bu-tree-row:hover { background: var(--bu-panel-alt); }
|
||||
.bu-source-partial > .bu-tree-row { background: rgba(255, 140, 47, .09); background: color-mix(in srgb, var(--bu-accent) 9%, transparent); }
|
||||
#urbm-app .bu-tree-toggle, #urbm-app .bu-tree-name { appearance: none; background: transparent !important; border: 0 !important; box-shadow: none !important; color: var(--bu-text) !important; margin: 0 !important; min-height: 0; padding: 0 !important; text-align: left; text-shadow: none !important; }
|
||||
#urbm-app .bu-tree-toggle { align-items: center; cursor: pointer; display: flex; height: 28px; justify-content: center; width: 28px; }
|
||||
.bu-tree-toggle span { display: inline-block; font-size: 14px; transition: transform .16s ease; }
|
||||
#urbm-app .bu-tree-toggle { align-items: center; background: var(--bu-panel-alt) !important; border: 1px solid var(--bu-border) !important; border-radius: 6px !important; cursor: pointer; display: flex; height: 30px; justify-content: center; width: 30px; }
|
||||
#urbm-app .bu-tree-toggle:hover { border-color: var(--bu-accent) !important; }
|
||||
.bu-tree-toggle span { display: inline-block; font-size: 13px; line-height: 1; transition: transform .16s ease; }
|
||||
.bu-tree-toggle.expanded span { transform: rotate(90deg); }
|
||||
#urbm-app .bu-tree-name { cursor: pointer; display: grid; gap: 1px; grid-template-columns: auto minmax(0, 1fr); min-width: 0; }
|
||||
.bu-tree-name > span:first-child { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
#urbm-app .bu-tree-name { cursor: pointer; display: grid; gap: 2px; grid-template-columns: auto minmax(0, 1fr); letter-spacing: normal !important; min-width: 0; text-transform: none !important; }
|
||||
.bu-tree-name > span:first-child { font-weight: 700; letter-spacing: normal; overflow: hidden; text-overflow: ellipsis; text-transform: none; white-space: nowrap; }
|
||||
.bu-tree-name small { color: var(--bu-muted); grid-column: 1 / -1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.bu-tree-children { position: relative; }
|
||||
.bu-tree-children::before { background: var(--bu-border); bottom: 3px; content: ''; left: calc((var(--bu-tree-depth, 0) + 1) * 22px + 18px); opacity: .45; position: absolute; top: 0; width: 1px; }
|
||||
|
||||
+15
-2
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user