From e6d369f4312f0c8d175251e149c62d08608cb18c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 4 Sep 2023 23:34:04 -0700 Subject: feat: :sparkles: autoscrolling --- extension/react-app/src/pages/gui.tsx | 93 +++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 27 deletions(-) (limited to 'extension/react-app/src/pages') diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index 60ad0c2c..3d98522a 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -99,24 +99,70 @@ function GUI(props: GUIProps) { ); }, [bottomMessage, aboveComboBoxDivRef.current]); - const [scrollTimeout, setScrollTimeout] = useState( - null + const [isScrolling, setIsScrolling] = useState(false); + + const [scrollTimeoutId, setScrollTimeoutId] = useState(); + + const scrollToBottom = useCallback( + (force: boolean = false) => { + if (scrollTimeoutId) { + return; + } + + // Scroll only if user is within 100 pixels of the bottom of the window. + const edgeOffset = 50; + const scrollPosition = window.scrollY || 0; + const scrollHeight = topGuiDivRef.current?.scrollHeight || 0; + const clientHeight = window.innerHeight || 0; + + const atBottomEdge = + scrollPosition + clientHeight + edgeOffset >= scrollHeight; + + if (isScrolling || (!atBottomEdge && !force)) { + return; + } + + window.scrollTo({ + top: scrollHeight, + behavior: "smooth", + }); + + setScrollTimeoutId( + setTimeout(() => { + setScrollTimeoutId(undefined); + }, 800) + ); + }, + [ + window.scrollY, + topGuiDivRef.current?.scrollHeight, + topGuiDivRef.current?.clientHeight, + isScrolling, + scrollTimeoutId, + ] ); - const scrollToBottom = useCallback(() => { - if (scrollTimeout) { - clearTimeout(scrollTimeout); - } - // Debounced smooth scroll to bottom of screen - if (topGuiDivRef.current) { - const timeout = setTimeout(() => { - window.scrollTo({ - top: topGuiDivRef.current!.offsetHeight, - behavior: "smooth", - }); - }, 200); - setScrollTimeout(timeout); - } - }, [topGuiDivRef.current, scrollTimeout]); + + const [timerId, setTimerId] = useState(); + + useEffect(() => { + const handleScrollEnd = () => { + setIsScrolling(false); + }; + + const handleScroll = () => { + window.clearTimeout(timerId); + + setIsScrolling(true); + + setTimerId(setTimeout(() => handleScrollEnd(), 800)); + }; + + window.addEventListener("scroll", handleScroll); + + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, []); useEffect(() => { // Cmd + Backspace to delete current step @@ -141,11 +187,6 @@ function GUI(props: GUIProps) { useEffect(() => { client?.onStateUpdate((state: FullState) => { - // Scroll only if user is at very bottom of the window. - const shouldScrollToBottom = - topGuiDivRef.current && - topGuiDivRef.current?.offsetHeight - window.scrollY < 100; - const waitingForSteps = state.active && state.history.current_index < state.history.timeline.length && @@ -177,14 +218,12 @@ function GUI(props: GUIProps) { return nextStepsOpen; }); - if (shouldScrollToBottom) { - scrollToBottom(); - } + scrollToBottom(); }); }, [client]); useEffect(() => { - scrollToBottom(); + scrollToBottom(true); }, [waitingForSteps]); // #endregion @@ -339,7 +378,7 @@ function GUI(props: GUIProps) { }, []); return (
{ if (e.key === "Enter" && e.ctrlKey) { -- cgit v1.2.3-70-g09d2