Fix log panel overlap and hover-peek dismissal

The empty Changes state (flex:1 with no min-height:0) overflowed .main and
drew over the log panel; add min-height:0/overflow clipping to .empty, .left
and .main so the panel gets its own space. Make the status-bar log-peek use
hover-intent (220ms delay + keep-open while hovering the popover) and sit
flush above the bar so it can be moused into and scrolled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bonchellon
2026-07-07 14:34:23 +03:00
parent 80f2acf40d
commit a67c171315
2 changed files with 20 additions and 11 deletions

View File

@ -97,8 +97,8 @@ body.resizing{cursor:col-resize!important;user-select:none}
@keyframes spin{to{transform:rotate(360deg)}}
/* main */
.main{flex:1;display:grid;grid-template-columns:var(--left-w,380px) 1fr;min-height:0}
.left{border-right:1px solid var(--border);display:flex;flex-direction:column;min-height:0}
.main{flex:1;display:grid;grid-template-columns:var(--left-w,380px) 1fr;min-height:0;overflow:hidden}
.left{border-right:1px solid var(--border);display:flex;flex-direction:column;min-height:0;overflow:hidden}
.right{display:flex;flex-direction:column;min-height:0}
.tabs{display:flex;height:46px;flex:0 0 46px;border-bottom:1px solid var(--border)}
@ -159,7 +159,7 @@ body.resizing{cursor:col-resize!important;user-select:none}
.st-a{color:var(--add);background:rgba(62,207,142,.12)}
.st-d{color:var(--del);background:rgba(242,99,126,.12)}
.empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--faint);font-size:13px;padding:30px;gap:10px;white-space:pre-line;line-height:1.5}
.empty{flex:1;min-height:0;overflow:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--faint);font-size:13px;padding:30px;gap:10px;white-space:pre-line;line-height:1.5}
.empty svg{width:34px;height:34px;opacity:.5}
.commit{border-top:1px solid var(--border);padding:14px;display:flex;flex-direction:column;gap:9px;background:var(--sunk)}
@ -250,7 +250,8 @@ body.resizing{cursor:col-resize!important;user-select:none}
/* bottom status bar (always visible) + hover log peek */
.statusbar{flex:0 0 26px;display:flex;align-items:center;gap:10px;padding:0 12px;border-top:1px solid var(--border);
background:var(--bar);font-size:11.5px;color:var(--muted);position:relative;user-select:none}
background:var(--bar);font-size:11.5px;color:var(--muted);position:relative;user-select:none;z-index:60}
.stfront{display:flex;align-items:center;gap:10px;flex:1;min-width:0;height:100%;cursor:default}
.stdot{width:16px;height:16px;display:grid;place-items:center;flex:0 0 auto;color:var(--add)}
.stdot svg{width:12px;height:12px}
.stdot.busy{color:var(--accent-2)}
@ -260,8 +261,10 @@ body.resizing{cursor:col-resize!important;user-select:none}
color:var(--muted);border-radius:7px;padding:3px 10px;cursor:pointer;font-size:11.5px}
.stlog:hover{color:var(--txt);border-color:var(--accent)}
.stlog svg{width:13px;height:13px}
.logpeek{position:absolute;right:10px;bottom:32px;width:460px;max-width:calc(100vw - 30px);max-height:300px;overflow:auto;z-index:250;
background:var(--elevated);border:1px solid var(--border);border-radius:11px;padding:6px;box-shadow:0 18px 46px -12px rgba(0,0,0,.6)}
.logpeek{position:absolute;left:8px;bottom:26px;width:520px;max-width:calc(100vw - 20px);max-height:min(340px,60vh);overflow-y:auto;z-index:250;
background:var(--elevated);border:1px solid var(--border);border-radius:11px 11px 0 0;padding:5px;box-shadow:0 -14px 40px -12px rgba(0,0,0,.55)}
.logpeek-h{position:sticky;top:0;padding:5px 9px 7px;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--faint);
background:var(--elevated);font-family:var(--font)}
/* log panel (Window → Log) — sits under the main area, above the status bar */
.logpanel{flex:0 0 190px;display:flex;flex-direction:column;min-height:0;border-top:1px solid var(--border);background:var(--sunk)}

View File

@ -226,6 +226,9 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
const [logOpen, setLogOpen] = useState(false);
const [peek, setPeek] = useState(false); // hover-over-loader log preview
const logId = useRef(0);
const peekTimer = useRef<number | null>(null);
function showPeek() { if (peekTimer.current) { clearTimeout(peekTimer.current); peekTimer.current = null; } setPeek(true); }
function hidePeek() { if (peekTimer.current) clearTimeout(peekTimer.current); peekTimer.current = window.setTimeout(() => setPeek(false), 220); }
// resizable panels (persisted): left panel width + Get-Latest zone width
const [leftW, setLeftW] = useState<number>(() => { const v = Number(localStorage.getItem("exd-left-w")); return v >= 280 ? v : 380; });
const [syncW, setSyncW] = useState<number>(() => { const v = Number(localStorage.getItem("exd-sync-w")); return v >= 150 ? v : 220; });
@ -634,13 +637,16 @@ function Workbench({ info, session, light, toggleTheme, lang, setLang, zoom, set
{logOpen && <LogPanel logs={logs} onClose={() => setLogOpen(false)} onClear={() => setLogs([])} />}
<div className="statusbar" onMouseEnter={() => setPeek(true)} onMouseLeave={() => setPeek(false)}>
<span className={"stdot" + ((busy || scanning || !!pushing) ? " busy" : "")}>{(busy || scanning || !!pushing) ? <span className="ldr sm" /> : I.check}</span>
<span className="stlast">{logs.length ? `${logs[logs.length - 1].cmd} ${logCount(logs[logs.length - 1])}` : "p4 — ready"}</span>
<div className="statusbar">
<span className="stfront" onMouseEnter={showPeek} onMouseLeave={hidePeek}>
<span className={"stdot" + ((busy || scanning || !!pushing) ? " busy" : "")}>{(busy || scanning || !!pushing) ? <span className="ldr sm" /> : I.check}</span>
<span className="stlast">{logs.length ? `${logs[logs.length - 1].cmd} ${logCount(logs[logs.length - 1])}` : "p4 — ready"}</span>
</span>
<button className="stlog" onClick={() => setLogOpen((o) => !o)} title="Ctrl+L">{I.log}<span>Log</span></button>
{peek && logs.length > 0 && (
<div className="logpeek">
{logs.slice(-14).map((l) => <LogRow key={l.id} l={l} />)}
<div className="logpeek" onMouseEnter={showPeek} onMouseLeave={hidePeek}>
<div className="logpeek-h">{t("Log")} · {logs.length}</div>
{logs.slice(-40).map((l) => <LogRow key={l.id} l={l} />)}
</div>
)}
</div>