Preserve tree scroll position

This commit is contained in:
Mikei386
2026-06-22 06:45:45 +02:00
parent d4f47c5bda
commit 91089328ce
7 changed files with 44 additions and 18 deletions
-1
View File
@@ -1 +0,0 @@
d7824cd92520205e3dafdd4681a4bcbe51f943f530785c8d8d4d7af88358a069 urbm-2026.06.22.r001-x86_64-1.txz
+1
View File
@@ -0,0 +1 @@
1fda0986918fa5838cbd8f56ce013e1c5a2add51c41fb5cee14fe77c0e33c0b4 urbm-2026.06.22.r002-x86_64-1.txz
+6 -2
View File
@@ -2,13 +2,17 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "urbm">
<!ENTITY author "Michael Roll">
<!ENTITY version "2026.06.22.r001">
<!ENTITY version "2026.06.22.r002">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
<!ENTITY packageSHA256 "d7824cd92520205e3dafdd4681a4bcbe51f943f530785c8d8d4d7af88358a069">
<!ENTITY packageSHA256 "1fda0986918fa5838cbd8f56ce013e1c5a2add51c41fb5cee14fe77c0e33c0b4">
]>
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
<CHANGES>
### 2026.06.22.r002
- Preserve page and tree-container scroll positions after expanding deeply nested folders in source, target, settings, repository, and snapshot trees.
- Restore scroll positions after the browser has processed focus/layout changes to prevent jumps back to the root.
### 2026.06.22.r001
- Prevent Unraid's global button styles from stretching folder tree disclosure controls over folder names.
- Reserve fixed columns for tree arrows and checkboxes so folder names no longer overlap with controls.
+5 -1
View File
@@ -2,13 +2,17 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "urbm">
<!ENTITY author "Michael Roll">
<!ENTITY version "2026.06.22.r001">
<!ENTITY version "2026.06.22.r002">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
<!ENTITY packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
]>
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
<CHANGES>
### 2026.06.22.r002
- Preserve page and tree-container scroll positions after expanding deeply nested folders in source, target, settings, repository, and snapshot trees.
- Restore scroll positions after the browser has processed focus/layout changes to prevent jumps back to the root.
### 2026.06.22.r001
- Prevent Unraid's global button styles from stretching folder tree disclosure controls over folder names.
- Reserve fixed columns for tree arrows and checkboxes so folder names no longer overlap with controls.
+4 -4
View File
@@ -9,12 +9,12 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
<?php
$pluginRoot = '/plugins/urbm';
?>
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260622r001">
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=20260622r002">
<div id="urbm-app" data-api="<?= $pluginRoot ?>/api.php">
<header class="bu-header">
<div class="bu-brand">
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260622r001" alt="URBM-Logo">
<div><h1>URBM <span class="bu-version">2026.06.22.r001</span></h1><p>Restic Backup Manager für Unraid</p></div>
<img class="bu-logo" src="<?= $pluginRoot ?>/images/urbm.png?v=20260622r002" alt="URBM-Logo">
<div><h1>URBM <span class="bu-version">2026.06.22.r002</span></h1><p>Restic Backup Manager für Unraid</p></div>
</div>
<div id="bu-health" class="bu-health" tabindex="0" data-tooltip="Zeigt, ob die URBM-Hintergrundkomponente erreichbar ist. Beispiel: 'Daemon online' bedeutet, dass Jobs gestartet werden können.">Verbindung wird hergestellt...</div>
</header>
@@ -32,4 +32,4 @@ $pluginRoot = '/plugins/urbm';
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
</div>
<script>window.URBM_CSRF = <?= json_encode($var['csrf_token'] ?? '') ?>;</script>
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.22.r001.js"></script>
<script src="<?= $pluginRoot ?>/assets/urbm-2026.06.22.r002.js"></script>
+28 -10
View File
@@ -375,6 +375,28 @@
}
}
function captureScrollState(root=document) {
const trees=[...root.querySelectorAll('.bu-tree')].map((tree,index)=>({index,top:tree.scrollTop,left:tree.scrollLeft}));
return {windowX:window.scrollX,windowY:window.scrollY,trees};
}
function restoreScrollState(snapshot, root=document) {
if(!snapshot) return;
const apply=()=>{
const trees=[...root.querySelectorAll('.bu-tree')];
snapshot.trees.forEach(item=>{
const tree=trees[item.index];
if(tree) {
tree.scrollTop=item.top;
tree.scrollLeft=item.left;
}
});
window.scrollTo(snapshot.windowX, snapshot.windowY);
};
apply();
requestAnimationFrame(()=>{ apply(); setTimeout(apply, 0); });
}
function renderDashboard() {
const active = state.runs.filter(r => ['queued','running','paused'].includes(r.status)).length;
const orderedRuns = newestRuns(state.runs);
@@ -517,15 +539,13 @@
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 scrollSnapshot=captureScrollState(host);
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);
restoreScrollState(scrollSnapshot, host);
}
async function toggleSourceTree(path, forceOpen=false) {
@@ -569,13 +589,11 @@
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 scrollSnapshot=captureScrollState(host);
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);
restoreScrollState(scrollSnapshot, host);
}
async function toggleDirectoryTree(key, path, forceOpen=false) {
@@ -755,9 +773,9 @@
return state.config;
}
function renderPreservingScroll() {
const windowX=window.scrollX, windowY=window.scrollY;
const scrollSnapshot=captureScrollState(content);
render();
window.scrollTo(windowX, windowY);
restoreScrollState(scrollSnapshot, content);
}
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;