diff --git a/frontend/src/pages/EventsPage.tsx b/frontend/src/pages/EventsPage.tsx index dc82b44..77bd492 100644 --- a/frontend/src/pages/EventsPage.tsx +++ b/frontend/src/pages/EventsPage.tsx @@ -36,6 +36,7 @@ export function EventsPage() { const [loadingMore, setLoadingMore] = useState(false); const [lastRefreshAt, setLastRefreshAt] = useState(null); const sentinelRef = useRef(null); + const feedRef = useRef(null); const refreshFirstPage = useCallback(async (replace = false) => { if (replace) setLoading(true); @@ -74,19 +75,12 @@ export function EventsPage() { } }, [loadingMore, nextPageUrl]); - useEffect(() => { - const node = sentinelRef.current; - if (!node || !nextPageUrl) return undefined; - - const observer = new IntersectionObserver( - (entries) => { - if (entries.some((entry) => entry.isIntersecting)) void loadMore(); - }, - { rootMargin: "280px 0px" } - ); - observer.observe(node); - return () => observer.disconnect(); - }, [loadMore, nextPageUrl]); + const handleFeedScroll = useCallback(() => { + const node = feedRef.current; + if (!node || !nextPageUrl || loadingMore) return; + const distanceFromBottom = node.scrollHeight - node.scrollTop - node.clientHeight; + if (distanceFromBottom <= 140) void loadMore(); + }, [loadMore, loadingMore, nextPageUrl]); const newestFirst = useMemo(() => [...events].sort((a, b) => b.id - a.id), [events]); @@ -113,7 +107,7 @@ export function EventsPage() { job-events.log refreshed {lastRefreshAt ? new Date(lastRefreshAt).toLocaleTimeString() : "-"} -
+
{newestFirst.map((event) => (
-
- {loadingMore && Loading older events...} - {!loadingMore && nextPageUrl && ( - - )} - {!loadingMore && !nextPageUrl && newestFirst.length > 0 && End of event history} +
+ {loadingMore && Loading older events...} + {!loadingMore && nextPageUrl && ( + + )} + {!loadingMore && !nextPageUrl && newestFirst.length > 0 && End of event history} +
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index f1ba34f..d19cb85 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -35,6 +35,9 @@ --panel-raised: #f9fbfc; --background: #f4f7f9; --shadow: 0 18px 46px rgb(35 36 38 / 8%); + --terminal-scroll-thumb: #0bb3f0; + --terminal-scroll-thumb-hover: #f0bb0b; + --terminal-scroll-track: #171b1f; color: var(--ink); background: var(--background); font-synthesis: none; @@ -60,6 +63,9 @@ --panel-raised: #25282b; --background: #141618; --shadow: 0 18px 46px rgb(0 0 0 / 28%); + --terminal-scroll-thumb: #f0bb0b; + --terminal-scroll-thumb-hover: #55c8ff; + --terminal-scroll-track: #101316; } * { @@ -1250,6 +1256,31 @@ label { min-height: 420px; overflow: auto; padding: 8px; + scrollbar-color: var(--terminal-scroll-thumb) var(--terminal-scroll-track); + scrollbar-width: thin; +} + +.terminal-feed::-webkit-scrollbar { + height: 10px; + width: 10px; +} + +.terminal-feed::-webkit-scrollbar-track { + background: var(--terminal-scroll-track); +} + +.terminal-feed::-webkit-scrollbar-thumb { + background: var(--terminal-scroll-thumb); + border: 2px solid var(--terminal-scroll-track); + border-radius: 999px; +} + +.terminal-feed::-webkit-scrollbar-thumb:hover { + background: var(--terminal-scroll-thumb-hover); +} + +.terminal-feed::-webkit-scrollbar-corner { + background: var(--terminal-scroll-track); } .terminal-line {