Portal action menus above Unraid layout
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
d486804b98406ddef9d29076a8e96ed71b73fb3921911a94b899f225ca5dd97d urbm-2026.07.13.r003-x86_64-1.txz
|
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
55ce788fc7ee7fbd005b4a08bb0eb3cd39b6386895e66f5741fe1100d8f173f8 urbm-2026.07.13.r004-x86_64-1.txz
|
||||||
Vendored
+6
-2
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.07.13.r003">
|
<!ENTITY version "2026.07.13.r004">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
<!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 packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "d486804b98406ddef9d29076a8e96ed71b73fb3921911a94b899f225ca5dd97d">
|
<!ENTITY packageSHA256 "55ce788fc7ee7fbd005b4a08bb0eb3cd39b6386895e66f5741fe1100d8f173f8">
|
||||||
]>
|
]>
|
||||||
<PLUGIN name="&name;" author="&author;" version="&version;" pluginURL="&pluginURL;" min="7.0.0" support="https://git.casaderoll.de/michael/URBM/issues" icon="urbm.png">
|
<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>
|
<CHANGES>
|
||||||
|
### 2026.07.13.r004
|
||||||
|
- Render open job and repository action menus in a fixed top-level overlay so Unraid table and card layers cannot clip them.
|
||||||
|
- Position the overlay against the triggering button and constrain it to the actually available viewport space.
|
||||||
|
|
||||||
### 2026.07.13.r003
|
### 2026.07.13.r003
|
||||||
- Keep repository and job action menus above following table rows and cards.
|
- Keep repository and job action menus above following table rows and cards.
|
||||||
- Automatically open long action menus upward when the available viewport space below is insufficient.
|
- Automatically open long action menus upward when the available viewport space below is insufficient.
|
||||||
|
|||||||
+5
-1
@@ -2,13 +2,17 @@
|
|||||||
<!DOCTYPE PLUGIN [
|
<!DOCTYPE PLUGIN [
|
||||||
<!ENTITY name "urbm">
|
<!ENTITY name "urbm">
|
||||||
<!ENTITY author "Michael Roll">
|
<!ENTITY author "Michael Roll">
|
||||||
<!ENTITY version "2026.07.13.r003">
|
<!ENTITY version "2026.07.13.r004">
|
||||||
<!ENTITY pluginURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm.plg">
|
<!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 packageURL "https://git.casaderoll.de/michael/URBM/raw/branch/main/dist/urbm-&version;-x86_64-1.txz">
|
||||||
<!ENTITY packageSHA256 "REPLACE_DURING_RELEASE">
|
<!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">
|
<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>
|
<CHANGES>
|
||||||
|
### 2026.07.13.r004
|
||||||
|
- Render open job and repository action menus in a fixed top-level overlay so Unraid table and card layers cannot clip them.
|
||||||
|
- Position the overlay against the triggering button and constrain it to the actually available viewport space.
|
||||||
|
|
||||||
### 2026.07.13.r003
|
### 2026.07.13.r003
|
||||||
- Keep repository and job action menus above following table rows and cards.
|
- Keep repository and job action menus above following table rows and cards.
|
||||||
- Automatically open long action menus upward when the available viewport space below is insufficient.
|
- Automatically open long action menus upward when the available viewport space below is insufficient.
|
||||||
|
|||||||
+2
-1
@@ -8,7 +8,7 @@ Tag="URBM Unraid Restic Backup Manager backup snapshots restore"
|
|||||||
---
|
---
|
||||||
<?php
|
<?php
|
||||||
$pluginRoot = '/plugins/urbm';
|
$pluginRoot = '/plugins/urbm';
|
||||||
$urbmVersion = '2026.07.13.r003';
|
$urbmVersion = '2026.07.13.r004';
|
||||||
$urbmAssetVersion = preg_replace('/[^A-Za-z0-9]/', '', $urbmVersion);
|
$urbmAssetVersion = preg_replace('/[^A-Za-z0-9]/', '', $urbmVersion);
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=<?= $urbmAssetVersion ?>">
|
<link rel="stylesheet" href="<?= $pluginRoot ?>/assets/urbm.css?v=<?= $urbmAssetVersion ?>">
|
||||||
@@ -33,6 +33,7 @@ $urbmAssetVersion = preg_replace('/[^A-Za-z0-9]/', '', $urbmVersion);
|
|||||||
<main id="bu-content" aria-live="polite"></main>
|
<main id="bu-content" aria-live="polite"></main>
|
||||||
<div id="bu-toast" role="status" aria-live="polite"></div>
|
<div id="bu-toast" role="status" aria-live="polite"></div>
|
||||||
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
<div id="bu-tooltip" role="tooltip" aria-hidden="true"></div>
|
||||||
|
<div id="bu-menu-portal" class="bu-menu-portal" aria-live="polite"></div>
|
||||||
<div id="bu-dialog" class="bu-dialog" role="dialog" aria-modal="true" aria-labelledby="bu-dialog-title" hidden>
|
<div id="bu-dialog" class="bu-dialog" role="dialog" aria-modal="true" aria-labelledby="bu-dialog-title" hidden>
|
||||||
<div class="bu-dialog-backdrop" data-dialog-cancel></div>
|
<div class="bu-dialog-backdrop" data-dialog-cancel></div>
|
||||||
<section class="bu-dialog-panel" tabindex="-1">
|
<section class="bu-dialog-panel" tabindex="-1">
|
||||||
|
|||||||
@@ -202,7 +202,8 @@
|
|||||||
#urbm-app .bu-action-menu > summary { align-items: center; display: inline-flex; gap: 8px; list-style: none; }
|
#urbm-app .bu-action-menu > summary { align-items: center; display: inline-flex; gap: 8px; list-style: none; }
|
||||||
.bu-action-menu > summary::-webkit-details-marker { display: none; }
|
.bu-action-menu > summary::-webkit-details-marker { display: none; }
|
||||||
.bu-action-menu-popover { background: var(--bu-panel); border: 1px solid var(--bu-border-strong); border-radius: 8px; box-shadow: 0 12px 32px rgba(0,0,0,.22); display: grid; max-height: min(430px, calc(100vh - 24px)); min-width: 220px; overflow-y: auto; padding: 6px; position: absolute; right: 0; top: calc(100% + 6px); z-index: 90; }
|
.bu-action-menu-popover { background: var(--bu-panel); border: 1px solid var(--bu-border-strong); border-radius: 8px; box-shadow: 0 12px 32px rgba(0,0,0,.22); display: grid; max-height: min(430px, calc(100vh - 24px)); min-width: 220px; overflow-y: auto; padding: 6px; position: absolute; right: 0; top: calc(100% + 6px); z-index: 90; }
|
||||||
.bu-action-menu.open-up .bu-action-menu-popover { bottom: calc(100% + 6px); top: auto; }
|
.bu-menu-portal { inset: 0; pointer-events: none; position: fixed; z-index: 100000; }
|
||||||
|
.bu-menu-portal .bu-action-menu-popover.portaled { pointer-events: auto; position: fixed; z-index: 100001; }
|
||||||
#urbm-app .bu-action-menu-popover .bu-button { background: transparent !important; border-color: transparent !important; justify-content: flex-start; text-align: left; width: 100%; }
|
#urbm-app .bu-action-menu-popover .bu-button { background: transparent !important; border-color: transparent !important; justify-content: flex-start; text-align: left; width: 100%; }
|
||||||
#urbm-app .bu-action-menu-popover .bu-button:hover { background: var(--bu-panel-alt) !important; border-color: transparent !important; }
|
#urbm-app .bu-action-menu-popover .bu-button:hover { background: var(--bu-panel-alt) !important; border-color: transparent !important; }
|
||||||
#urbm-app .bu-action-menu-popover .bu-button.danger { border-top: 1px solid var(--bu-border) !important; border-radius: 0 !important; margin-top: 4px !important; }
|
#urbm-app .bu-action-menu-popover .bu-button.danger { border-top: 1px solid var(--bu-border) !important; border-radius: 0 !important; margin-top: 4px !important; }
|
||||||
|
|||||||
+41
-6
@@ -6,6 +6,7 @@
|
|||||||
const health = document.getElementById('bu-health');
|
const health = document.getElementById('bu-health');
|
||||||
const toast = document.getElementById('bu-toast');
|
const toast = document.getElementById('bu-toast');
|
||||||
const tooltip = document.getElementById('bu-tooltip');
|
const tooltip = document.getElementById('bu-tooltip');
|
||||||
|
const menuPortal = document.getElementById('bu-menu-portal');
|
||||||
const dialog = document.getElementById('bu-dialog');
|
const dialog = document.getElementById('bu-dialog');
|
||||||
const sourceRoots = ['/mnt/user','/mnt/disks','/mnt/remotes','/boot'].map(path=>({name:path,path}));
|
const sourceRoots = ['/mnt/user','/mnt/disks','/mnt/remotes','/boot'].map(path=>({name:path,path}));
|
||||||
const repositoryRoots = ['/mnt/user','/mnt/disks','/mnt/remotes'].map(path=>({name:path,path}));
|
const repositoryRoots = ['/mnt/user','/mnt/disks','/mnt/remotes'].map(path=>({name:path,path}));
|
||||||
@@ -422,6 +423,7 @@
|
|||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const views = {dashboard:renderDashboard, jobs:renderJobs, repositories:renderRepositories, snapshots:renderSnapshots, restore:renderRestore, activity:renderActivity, logs:renderLogs, settings:renderSettings};
|
const views = {dashboard:renderDashboard, jobs:renderJobs, repositories:renderRepositories, snapshots:renderSnapshots, restore:renderRestore, activity:renderActivity, logs:renderLogs, settings:renderSettings};
|
||||||
|
menuPortal?.replaceChildren();
|
||||||
content.innerHTML = views[state.view]();
|
content.innerHTML = views[state.view]();
|
||||||
enhanceTooltips(content);
|
enhanceTooltips(content);
|
||||||
content.querySelectorAll('.bu-file-select[data-partial="true"]').forEach(box=>{box.indeterminate=true;box.setAttribute('aria-checked','mixed');});
|
content.querySelectorAll('.bu-file-select[data-partial="true"]').forEach(box=>{box.indeterminate=true;box.setAttribute('aria-checked','mixed');});
|
||||||
@@ -966,7 +968,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector('.bu-tabs').addEventListener('click', e => { const button=e.target.closest('[data-view]'); if(!button)return; state.view=button.dataset.view; document.querySelectorAll('.bu-tabs button').forEach(x=>x.classList.toggle('active',x===button)); render(); if(state.view==='dashboard')loadRepositoryStats(); if(state.view==='logs'&&!state.logs&&!state.logsLoading)loadLogs(); });
|
document.querySelector('.bu-tabs').addEventListener('click', e => { const button=e.target.closest('[data-view]'); if(!button)return; state.view=button.dataset.view; document.querySelectorAll('.bu-tabs button').forEach(x=>x.classList.toggle('active',x===button)); render(); if(state.view==='dashboard')loadRepositoryStats(); if(state.view==='logs'&&!state.logs&&!state.logsLoading)loadLogs(); });
|
||||||
content.addEventListener('click', e => { const target=e.target.closest('[data-action]'); if(!target)return; e.preventDefault(); target.closest('.bu-action-menu')?.removeAttribute('open'); handle(target.dataset.action,target); });
|
function handleActionClick(e) {
|
||||||
|
const target=e.target.closest('[data-action]');
|
||||||
|
if(!target) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const popover=target.closest('.bu-action-menu-popover');
|
||||||
|
const menu=target.closest('.bu-action-menu')||popover?._ownerMenu;
|
||||||
|
menu?.removeAttribute('open');
|
||||||
|
handle(target.dataset.action,target);
|
||||||
|
}
|
||||||
|
content.addEventListener('click', handleActionClick);
|
||||||
|
menuPortal?.addEventListener('click', handleActionClick);
|
||||||
content.addEventListener('input', e => {
|
content.addEventListener('input', e => {
|
||||||
const target=e.target.closest('[data-filter-table]');
|
const target=e.target.closest('[data-filter-table]');
|
||||||
if(!target) return;
|
if(!target) return;
|
||||||
@@ -975,27 +987,50 @@
|
|||||||
table?.querySelectorAll('tbody tr[data-search]').forEach(row=>{ row.hidden=Boolean(query)&&!row.dataset.search.includes(query); });
|
table?.querySelectorAll('tbody tr[data-search]').forEach(row=>{ row.hidden=Boolean(query)&&!row.dataset.search.includes(query); });
|
||||||
});
|
});
|
||||||
document.addEventListener('click', e=>{
|
document.addEventListener('click', e=>{
|
||||||
document.querySelectorAll('.bu-action-menu[open]').forEach(menu=>{ if(!menu.contains(e.target)) menu.removeAttribute('open'); });
|
document.querySelectorAll('.bu-action-menu[open]').forEach(menu=>{
|
||||||
|
const popover=menu._popover;
|
||||||
|
if(!menu.contains(e.target)&&!popover?.contains(e.target)) menu.removeAttribute('open');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
content.addEventListener('toggle', e=>{
|
content.addEventListener('toggle', e=>{
|
||||||
const menu=e.target.closest?.('.bu-action-menu');
|
const menu=e.target.closest?.('.bu-action-menu');
|
||||||
if(!menu) return;
|
if(!menu) return;
|
||||||
const row=menu.closest('tr');
|
const row=menu.closest('tr');
|
||||||
row?.classList.toggle('bu-menu-open',menu.open);
|
row?.classList.toggle('bu-menu-open',menu.open);
|
||||||
menu.classList.remove('open-up');
|
if(!menu.open) {
|
||||||
if(!menu.open) return;
|
const popover=menu._popover;
|
||||||
|
if(popover) {
|
||||||
|
popover.removeAttribute('style');
|
||||||
|
popover.classList.remove('portaled');
|
||||||
|
delete popover._ownerMenu;
|
||||||
|
menu.appendChild(popover);
|
||||||
|
delete menu._popover;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
content.querySelectorAll('.bu-action-menu[open]').forEach(other=>{ if(other!==menu) other.removeAttribute('open'); });
|
content.querySelectorAll('.bu-action-menu[open]').forEach(other=>{ if(other!==menu) other.removeAttribute('open'); });
|
||||||
requestAnimationFrame(()=>{
|
requestAnimationFrame(()=>{
|
||||||
const summary=menu.querySelector('summary');
|
const summary=menu.querySelector('summary');
|
||||||
const popover=menu.querySelector('.bu-action-menu-popover');
|
const popover=menu.querySelector('.bu-action-menu-popover');
|
||||||
if(!summary||!popover) return;
|
if(!summary||!popover||!menuPortal) return;
|
||||||
const rect=summary.getBoundingClientRect();
|
const rect=summary.getBoundingClientRect();
|
||||||
|
menu._popover=popover;
|
||||||
|
popover._ownerMenu=menu;
|
||||||
|
popover.classList.add('portaled');
|
||||||
|
menuPortal.appendChild(popover);
|
||||||
|
const width=Math.min(Math.max(popover.offsetWidth,220),window.innerWidth-16);
|
||||||
const needed=Math.min(popover.scrollHeight,430);
|
const needed=Math.min(popover.scrollHeight,430);
|
||||||
const below=window.innerHeight-rect.bottom-12;
|
const below=window.innerHeight-rect.bottom-12;
|
||||||
const above=rect.top-12;
|
const above=rect.top-12;
|
||||||
menu.classList.toggle('open-up',below<needed&&above>below);
|
const openUp=below<needed&&above>below;
|
||||||
|
const available=Math.max(120,(openUp?above:below)-6);
|
||||||
|
const height=Math.min(needed,available);
|
||||||
|
const left=Math.max(8,Math.min(rect.right-width,window.innerWidth-width-8));
|
||||||
|
const top=openUp?Math.max(8,rect.top-height-6):rect.bottom+6;
|
||||||
|
Object.assign(popover.style,{left:`${left}px`,right:'auto',top:`${top}px`,bottom:'auto',width:`${width}px`,maxHeight:`${height}px`});
|
||||||
});
|
});
|
||||||
},true);
|
},true);
|
||||||
|
window.addEventListener('resize',()=>document.querySelectorAll('.bu-action-menu[open]').forEach(menu=>menu.removeAttribute('open')));
|
||||||
content.addEventListener('change', e => {
|
content.addEventListener('change', e => {
|
||||||
if (e.target.name === 'type' && e.target.closest('#bu-repo-form')) {
|
if (e.target.name === 'type' && e.target.closest('#bu-repo-form')) {
|
||||||
renderRepositorySpecific(e.target.value);
|
renderRepositorySpecific(e.target.value);
|
||||||
|
|||||||
Reference in New Issue
Block a user