Add auto-scroll control (pin/unpin) to session output
This commit is contained in:
parent
68dce06e47
commit
737948c820
1 changed files with 112 additions and 2 deletions
|
|
@ -194,12 +194,45 @@
|
|||
border-top: 1px solid #3a3a3a;
|
||||
max-height: var(--terminal-height);
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.session.expanded .session-output {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pin-scroll-btn {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
background: rgba(42, 42, 42, 0.9);
|
||||
border: 1px solid #3a3a3a;
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, opacity 0.2s;
|
||||
z-index: 10;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pin-scroll-btn:hover {
|
||||
background: rgba(58, 58, 58, 0.95);
|
||||
}
|
||||
|
||||
.pin-scroll-btn:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.pin-scroll-btn.pinned {
|
||||
background: rgba(255, 152, 0, 0.2);
|
||||
border-color: #ff9800;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
|
||||
font-size: var(--font-size);
|
||||
|
|
@ -1009,6 +1042,7 @@
|
|||
idle_since: null,
|
||||
git_branch: null,
|
||||
git_files_json: null,
|
||||
autoScroll: true,
|
||||
});
|
||||
renderSessions();
|
||||
});
|
||||
|
|
@ -1280,11 +1314,24 @@
|
|||
<div class="session-toggle">▼</div>
|
||||
</div>
|
||||
${renderStatsWidget(s)}
|
||||
<div class="session-output">
|
||||
<div class="session-output" id="session-output-${s.id}">
|
||||
<button class="pin-scroll-btn ${s.autoScroll ? 'pinned' : ''}"
|
||||
onclick="toggleAutoScroll(${s.id}); event.stopPropagation();"
|
||||
title="${s.autoScroll ? 'Auto-scroll enabled' : 'Auto-scroll disabled'}">
|
||||
${s.autoScroll ? '📌' : '📍'}
|
||||
</button>
|
||||
<div class="terminal" id="output-${s.id}">${s.output}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Attach scroll listeners after rendering
|
||||
sessionsToRender.forEach(s => {
|
||||
const $output = document.getElementById(`session-output-${s.id}`);
|
||||
if ($output) {
|
||||
attachScrollListener(s.id, $output);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderSessionOutput(sessionId) {
|
||||
|
|
@ -1299,7 +1346,8 @@
|
|||
$output.innerHTML += newChunk;
|
||||
session.outputRenderedLength = session.output.length;
|
||||
}
|
||||
if (session.expanded) {
|
||||
// Only auto-scroll if session is expanded and autoScroll is enabled
|
||||
if (session.expanded && session.autoScroll) {
|
||||
$output.parentElement.scrollTop = $output.parentElement.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
|
@ -1915,6 +1963,68 @@
|
|||
}).join('');
|
||||
}
|
||||
|
||||
// Auto-scroll control
|
||||
const scrollListeners = new Map();
|
||||
|
||||
function attachScrollListener(sessionId, $outputContainer) {
|
||||
// Remove existing listener if present
|
||||
const existing = scrollListeners.get(sessionId);
|
||||
if (existing) {
|
||||
$outputContainer.removeEventListener('scroll', existing);
|
||||
}
|
||||
|
||||
const listener = () => {
|
||||
const session = state.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
|
||||
const { scrollTop, clientHeight, scrollHeight } = $outputContainer;
|
||||
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
|
||||
|
||||
if (session.autoScroll !== isAtBottom) {
|
||||
session.autoScroll = isAtBottom;
|
||||
updatePinButton(sessionId);
|
||||
}
|
||||
};
|
||||
|
||||
$outputContainer.addEventListener('scroll', listener);
|
||||
scrollListeners.set(sessionId, listener);
|
||||
}
|
||||
|
||||
window.toggleAutoScroll = (sessionId) => {
|
||||
const session = state.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
|
||||
session.autoScroll = !session.autoScroll;
|
||||
|
||||
// If enabling auto-scroll, scroll to bottom immediately
|
||||
if (session.autoScroll) {
|
||||
const $outputContainer = document.getElementById(`session-output-${sessionId}`);
|
||||
if ($outputContainer) {
|
||||
$outputContainer.scrollTop = $outputContainer.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
updatePinButton(sessionId);
|
||||
};
|
||||
|
||||
function updatePinButton(sessionId) {
|
||||
const session = state.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
|
||||
const $btn = document.querySelector(`#session-output-${sessionId} .pin-scroll-btn`);
|
||||
if ($btn) {
|
||||
if (session.autoScroll) {
|
||||
$btn.classList.add('pinned');
|
||||
$btn.textContent = '📌';
|
||||
$btn.title = 'Auto-scroll enabled';
|
||||
} else {
|
||||
$btn.classList.remove('pinned');
|
||||
$btn.textContent = '📍';
|
||||
$btn.title = 'Auto-scroll disabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadSettings();
|
||||
applySettings();
|
||||
|
|
|
|||
Loading…
Reference in a new issue