diff options
Diffstat (limited to 'extension/react-app/src/pages')
| -rw-r--r-- | extension/react-app/src/pages/gui.tsx | 93 | 
1 files changed, 66 insertions, 27 deletions
| 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<NodeJS.Timeout | null>( -    null +  const [isScrolling, setIsScrolling] = useState(false); + +  const [scrollTimeoutId, setScrollTimeoutId] = useState<NodeJS.Timeout>(); + +  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<NodeJS.Timeout>(); + +  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 (      <div -      className="overflow-hidden" +      className="overflow-scroll"        ref={topGuiDivRef}        onKeyDown={(e) => {          if (e.key === "Enter" && e.ctrlKey) { | 
