diff --git a/internal/server/web/static/app.css b/internal/server/web/static/app.css index 2cd38b9..8a089d7 100644 --- a/internal/server/web/static/app.css +++ b/internal/server/web/static/app.css @@ -340,6 +340,10 @@ body.multi .area { margin-top:0; border-top-left-radius:0; } body.split1 .float-add { top:42px; right:16px; } .float-add:hover, .float-add:focus-visible { opacity:1; background:var(--panel); } body.multi .float-add { opacity:0; pointer-events:none; } +.float-add { transition:opacity .25s ease, background .25s ease, top .3s ease, right .3s ease, width .3s ease, height .3s ease, border-radius .3s ease; } +/* In a file pane's top bar: a button like its neighbours. */ +.float-add.in-files { width:34px; height:34px; font-size:19px; opacity:1; border-radius:8px; } +.float-add.in-files:hover { background:#151a21; } .tabview { position:absolute; inset:8px; opacity:0; visibility:hidden; transform:translateY(4px) scale(.995); transition:opacity .22s ease, transform .22s ease, visibility 0s .22s; } /* A tab fading out takes no pointer: a drop goes to the tab coming in. */ diff --git a/internal/server/web/static/workspace.js b/internal/server/web/static/workspace.js index 1dcb293..05c83ea 100644 --- a/internal/server/web/static/workspace.js +++ b/internal/server/web/static/workspace.js @@ -101,6 +101,8 @@ t.el.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' }); if (desk && activePane) activePane.focus(); updateMode(); + // The tab comes in moving a little; the "+" goes by where it ends up. + setTimeout(placeFloat, 260); } function setActivePane(s) { activePane = s; @@ -118,6 +120,20 @@ // The corner "+" moves down out of the way of a split tab's title bars. function syncFloat() { document.body.classList.toggle('split1', tabs.length === 1 && tabs[0].panes.length > 1); + placeFloat(); + } + // With one tab and a file pane in the top right corner, the "+" joins + // that pane's top bar: right of "Обновить", above "Удалить выбранное", + // a button of the same size. Otherwise it is back in the corner. + function placeFloat() { + const p = tabs.length === 1 ? tabs[0].panes.find(s => s.el.classList.contains('under-plus')) : null; + const bars = p && /files$/.test(p.kind) ? p.el.querySelectorAll('.fm-bar') : []; + const on = bars.length > 1 && bars[0].offsetParent !== null; + floatAdd.classList.toggle('in-files', on); + if (!on) { floatAdd.style.top = floatAdd.style.right = ''; return; } + const a = area.getBoundingClientRect(), top = bars[0].getBoundingClientRect(), row = bars[1].getBoundingClientRect(); + floatAdd.style.top = (top.top - a.top - area.clientTop) + 'px'; + floatAdd.style.right = (a.left + area.clientLeft + area.clientWidth - row.right) + 'px'; } // One tab: a plain terminal, the strip folded into the corner "+". function updateMode() { @@ -223,7 +239,7 @@ g.style.left = d.x + 'px'; g.style.top = d.y + 'px'; g.style.width = d.w + 'px'; g.style.height = d.h + 'px'; }); clearTimeout(t.fitTimer); - t.fitTimer = setTimeout(() => t.panes.forEach(s => s.fit()), animate ? 270 : 0); + t.fitTimer = setTimeout(() => { t.panes.forEach(s => s.fit()); placeFloat(); }, animate ? 270 : 0); } function startResize(ev, t, g) { ev.preventDefault();