fix(logs): disable auto-scroll on user scroll-up, re-enable on scroll-to-bottom

Add wheel, touch, and keyboard event handlers to log containers in
deployment and get-logs views. Auto-follow disables when user scrolls
up; re-enables when user scrolls back to bottom (within 10px threshold).
This commit is contained in:
Andras Bacsai 2026-04-28 10:33:08 +02:00
parent f2ac6da98e
commit 9a58e0fea2
4 changed files with 208 additions and 55 deletions

View file

@ -9,6 +9,9 @@
fullscreen: @entangle('fullscreen'),
alwaysScroll: {{ $isKeepAliveOn ? 'true' : 'false' }},
rafId: null,
scrollDebounce: null,
isScrolling: false,
lastTouchY: 0,
showTimestamps: true,
searchQuery: '',
matchCount: 0,
@ -19,9 +22,54 @@
scrollToBottom() {
const logsContainer = document.getElementById('logsContainer');
if (logsContainer) {
this.isScrolling = true;
logsContainer.scrollTop = logsContainer.scrollHeight;
setTimeout(() => { this.isScrolling = false; }, 50);
}
},
disableFollow() {
if (!this.alwaysScroll) return;
this.alwaysScroll = false;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
},
handleWheel(event) {
if (this.alwaysScroll && event.deltaY < 0) {
this.disableFollow();
}
},
handleTouchStart(event) {
this.lastTouchY = event.touches[0].clientY;
},
handleTouchMove(event) {
if (!this.alwaysScroll) return;
const currentY = event.touches[0].clientY;
if (currentY > this.lastTouchY) {
this.disableFollow();
}
this.lastTouchY = currentY;
},
handleKeyScroll(event) {
if (!this.alwaysScroll) return;
const upKeys = ['ArrowUp', 'PageUp', 'Home'];
if (upKeys.includes(event.key)) {
this.disableFollow();
}
},
handleScroll(event) {
if (this.isScrolling) return;
clearTimeout(this.scrollDebounce);
this.scrollDebounce = setTimeout(() => {
const el = event.target;
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
if (!this.alwaysScroll && distanceFromBottom <= 10) {
this.alwaysScroll = true;
this.scheduleScroll();
}
}, 150);
},
scheduleScroll() {
if (!this.alwaysScroll) return;
this.rafId = requestAnimationFrame(() => {
@ -327,7 +375,8 @@ class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-
</div>
</div>
</div>
<div id="logsContainer"
<div id="logsContainer" @scroll="handleScroll" @wheel="handleWheel"
@touchstart="handleTouchStart" @touchmove="handleTouchMove" @keydown="handleKeyScroll" tabindex="0"
class="flex min-h-40 flex-1 flex-col overflow-y-auto p-2 px-4 scrollbar">
<div id="logs" class="flex flex-col font-logs">
<div x-show="searchQuery.trim() && matchCount === 0"

View file

@ -28,6 +28,38 @@
}
},
isScrolling: false,
lastTouchY: 0,
disableFollow() {
if (!this.alwaysScroll) return;
this.alwaysScroll = false;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
},
handleWheel(event) {
if (this.alwaysScroll && event.deltaY < 0) {
this.disableFollow();
}
},
handleTouchStart(event) {
this.lastTouchY = event.touches[0].clientY;
},
handleTouchMove(event) {
if (!this.alwaysScroll) return;
const currentY = event.touches[0].clientY;
if (currentY > this.lastTouchY) {
this.disableFollow();
}
this.lastTouchY = currentY;
},
handleKeyScroll(event) {
if (!this.alwaysScroll) return;
const upKeys = ['ArrowUp', 'PageUp', 'Home'];
if (upKeys.includes(event.key)) {
this.disableFollow();
}
},
scrollToBottom() {
const logsContainer = document.getElementById('logsContainer');
if (logsContainer) {
@ -57,17 +89,14 @@
}
},
handleScroll(event) {
if (!this.alwaysScroll || this.isScrolling) return;
if (this.isScrolling) return;
clearTimeout(this.scrollDebounce);
this.scrollDebounce = setTimeout(() => {
const el = event.target;
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight;
if (distanceFromBottom > 100) {
this.alwaysScroll = false;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (!this.alwaysScroll && distanceFromBottom <= 10) {
this.alwaysScroll = true;
this.scheduleScroll();
}
}, 150);
},
@ -473,7 +502,8 @@ class="p-1 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-
</div>
</div>
</div>
<div id="logsContainer" @scroll="handleScroll"
<div id="logsContainer" @scroll="handleScroll" @wheel="handleWheel"
@touchstart="handleTouchStart" @touchmove="handleTouchMove" @keydown="handleKeyScroll" tabindex="0"
class="flex overflow-y-auto overflow-x-hidden flex-col px-4 py-2 w-full min-w-0 scrollbar"
:class="fullscreen ? 'flex-1' : 'max-h-[40rem]'">
@if ($outputs)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long