From 80bfd05be4b433fc432b5b8752fdd17aec14b381 Mon Sep 17 00:00:00 2001 From: Bobby Date: Fri, 18 Jun 2021 19:23:56 +1000 Subject: [PATCH 1/3] ft build stream: auto scroll to latest log --- www/package.json | 1 + .../Table/TableHeader/TableLogs.tsx | 83 +++++++++++++++++-- www/yarn.lock | 5 ++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/www/package.json b/www/package.json index 8d616972..82248a27 100644 --- a/www/package.json +++ b/www/package.json @@ -49,6 +49,7 @@ "react-router-dom": "^5.0.1", "react-scripts": "^3.4.3", "react-scroll-sync": "^0.8.0", + "react-usestateref": "^1.0.5", "serve": "^11.3.2", "tinymce": "^5.2.0", "typescript": "^3.7.2", diff --git a/www/src/components/Table/TableHeader/TableLogs.tsx b/www/src/components/Table/TableHeader/TableLogs.tsx index 2e181fb6..33f7f2e4 100644 --- a/www/src/components/Table/TableHeader/TableLogs.tsx +++ b/www/src/components/Table/TableHeader/TableLogs.tsx @@ -1,12 +1,14 @@ -import React, { useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import useRouter from "hooks/useRouter"; import useTable from "hooks/useFiretable/useTable"; import { useFiretableContext } from "contexts/FiretableContext"; +import useStateRef from "react-usestateref"; import _camelCase from "lodash/camelCase"; import _get from "lodash/get"; import _find from "lodash/find"; import _sortBy from "lodash/sortBy"; +import _throttle from "lodash/throttle"; import moment from "moment"; import { @@ -57,6 +59,10 @@ const useStyles = makeStyles((theme) => ({ width: "100%", backgroundColor: "#1E1E1E", }, + logPanelProgress: { + marginLeft: "3em", + marginTop: "1em", + }, logEntryWrapper: { overflowY: "scroll", maxHeight: "100%", @@ -136,23 +142,88 @@ function LogRow({ logRecord, index }) { } function LogPanel(props) { - const { logs, status, value, index, ...other } = props; + const { logs, status, value, index, isOpen, ...other } = props; const classes = useStyles(); + // useStateRef is necessary to resolve the state syncing issue + // https://stackoverflow.com/a/63039797/12208834 + const [liveStreaming, setLiveStreaming, liveStreamingStateRef] = useStateRef( + true + ); + const liveStreamingRef = useRef(); + const isActive = value === index; + + const handleScroll = _throttle(() => { + const target = document.querySelector("#live-stream-target"); + const scrollBox = document.querySelector("#live-stream-scroll-box"); + const liveStreamTargetVisible = isTargetInsideBox(target, scrollBox); + if (liveStreamTargetVisible !== liveStreamingStateRef.current) { + console.log("live streaming:", liveStreamTargetVisible); + setLiveStreaming(liveStreamTargetVisible); + } + }, 100); + + const scrollToLive = () => { + const liveStreamTarget = document.querySelector("#live-stream-target"); + liveStreamTarget?.scrollIntoView?.({ + behavior: "smooth", + }); + }; + + const isTargetInsideBox = (target, box) => { + const targetRect = target.getBoundingClientRect(); + const boxRect = box.getBoundingClientRect(); + return targetRect.y < boxRect.y + boxRect.height; + }; + + useEffect(() => { + if (liveStreaming && isActive && status === "BUILDING") { + if (!liveStreamingRef.current) { + scrollToLive(); + } else { + setTimeout(scrollToLive, 100); + } + } + }, [logs, value]); + + useEffect(() => { + if (isActive) { + const liveStreamScrollBox = document.querySelector( + "#live-stream-scroll-box" + ); + liveStreamScrollBox!.addEventListener("scroll", () => { + handleScroll(); + }); + } + }, [value]); + return (