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:
15
src/App.css
15
src/App.css
@ -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)}
|
||||
|
||||
16
src/App.tsx
16
src/App.tsx
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user