Portal action menus above Unraid layout

This commit is contained in:
Mikei386
2026-07-13 20:08:39 +02:00
parent 4cd2b7da47
commit a66540d2fb
8 changed files with 57 additions and 12 deletions
+41 -6
View File
@@ -6,6 +6,7 @@
const health = document.getElementById('bu-health');
const toast = document.getElementById('bu-toast');
const tooltip = document.getElementById('bu-tooltip');
const menuPortal = document.getElementById('bu-menu-portal');
const dialog = document.getElementById('bu-dialog');
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}));
@@ -422,6 +423,7 @@
function render() {
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]();
enhanceTooltips(content);
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(); });
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 => {
const target=e.target.closest('[data-filter-table]');
if(!target) return;
@@ -975,27 +987,50 @@
table?.querySelectorAll('tbody tr[data-search]').forEach(row=>{ row.hidden=Boolean(query)&&!row.dataset.search.includes(query); });
});
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=>{
const menu=e.target.closest?.('.bu-action-menu');
if(!menu) return;
const row=menu.closest('tr');
row?.classList.toggle('bu-menu-open',menu.open);
menu.classList.remove('open-up');
if(!menu.open) return;
if(!menu.open) {
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'); });
requestAnimationFrame(()=>{
const summary=menu.querySelector('summary');
const popover=menu.querySelector('.bu-action-menu-popover');
if(!summary||!popover) return;
if(!summary||!popover||!menuPortal) return;
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 below=window.innerHeight-rect.bottom-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);
window.addEventListener('resize',()=>document.querySelectorAll('.bu-action-menu[open]').forEach(menu=>menu.removeAttribute('open')));
content.addEventListener('change', e => {
if (e.target.name === 'type' && e.target.closest('#bu-repo-form')) {
renderRepositorySpecific(e.target.value);