diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index be6eeb74d..4fafc584b 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -4,6 +4,7 @@ import BottomBlock from '../BottomBlock'; import { Tabs, Input, Icon, NoContent } from 'UI'; import cn from 'classnames'; import ConsoleRow from '../ConsoleRow'; +import useLatestRef from 'App/hooks/useLatestRef' import { getRE } from 'App/utils'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; @@ -11,6 +12,7 @@ import { List, CellMeasurer, CellMeasurerCache, AutoSizer } from 'react-virtuali import { useStore } from 'App/mstore'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import { useModal } from 'App/components/Modal'; +import useAutoscroll from '../useAutoscroll'; const ALL = 'ALL'; const INFO = 'INFO'; @@ -57,7 +59,6 @@ const getIconProps = (level: any) => { const INDEX_KEY = 'console'; -const TIMEOUT_DURATION = 5000; function ConsolePanel() { const { @@ -70,7 +71,6 @@ function ConsolePanel() { const activeIndex = devTools[INDEX_KEY].index; const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); const [filteredList, setFilteredList] = useState([]); - const [autoScroll, setAutoScroll] = useState(activeIndex === 0); const { showModal } = useModal(); const [logs, setLogs] = useState([]) @@ -95,48 +95,24 @@ function ConsolePanel() { const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }) const onFilterChange = ({ target: { value } }: any) => devTools.update(INDEX_KEY, { filter: value }) - - const autoScrollIndex = logListNow.length + exceptionsListNow.length - // AutoScroll index update - useEffect(() => { - if (autoScroll && autoScrollIndex !== activeIndex) { - devTools.update(INDEX_KEY, { index: autoScrollIndex }) // can just scroll here - } - }, [autoScroll, autoScrollIndex]) - const timeoutIDRef = React.useRef>() - const timeoutStartAutoScroll = () => { - clearTimeout(timeoutIDRef.current); - timeoutIDRef.current = setTimeout(() => { - setAutoScroll(true) - }, TIMEOUT_DURATION); - } - const stopAutoScroll = () => { - clearTimeout(timeoutIDRef.current) - setAutoScroll(false) - } - const onMouseEnter = stopAutoScroll + // AutoScroll + const autoScrollIndex = logListNow.length + exceptionsListNow.length + const { + timeoutStartAutoscroll, + stopAutoscroll, + } = useAutoscroll( + activeIndex, + autoScrollIndex, + index => devTools.update(INDEX_KEY, { index }) + ) + + const onMouseEnter = stopAutoscroll const onMouseLeave = () => { if (isDetailsModalActive) { return } - timeoutStartAutoScroll() + timeoutStartAutoscroll() } - - // latest ref - const autoScrollRef = useRef(autoScroll) - useEffect(() => { autoScrollRef.current = autoScroll }, [ autoScroll ]) - useEffect(() => { - if (!autoScroll) { - timeoutStartAutoScroll() - } - return () => { - clearTimeout(timeoutIDRef.current) - if (autoScrollRef.current) { - devTools.update(INDEX_KEY, { index: 0 }) // index:0 means autoscroll is active - } - } - }, []) - const cache = new CellMeasurerCache({ fixedWidth: true, keyMapper: (index: number) => filteredList[index], @@ -158,11 +134,11 @@ function ConsolePanel() { right: true, onClose: () => { setIsDetailsModalActive(false) - timeoutStartAutoScroll() + timeoutStartAutoscroll() } }); devTools.update(INDEX_KEY, { index: filteredList.indexOf(log) }); - stopAutoScroll() + stopAutoscroll() } const _rowRenderer = ({ index, key, parent, style }: any) => { const item = filteredList[index]; diff --git a/frontend/app/components/shared/DevTools/useAutoscroll.ts b/frontend/app/components/shared/DevTools/useAutoscroll.ts new file mode 100644 index 000000000..1fcd13248 --- /dev/null +++ b/frontend/app/components/shared/DevTools/useAutoscroll.ts @@ -0,0 +1,43 @@ +import { useEffect, useState, useRef } from 'react' +import useLatestRef from 'App/hooks/useLatestRef' +import useCancelableTimeout from 'App/hooks/useCancelableTimeout' + +const TIMEOUT_DURATION = 5000; + +export default function useAutoscroll( + savedIndex: number, + autoscrollIndex: number, + updadteIndex: (index: number) => void, +) { + const [ autoscroll, setAutoscroll ] = useState(savedIndex === 0) + + const [ timeoutStartAutoscroll, stopAutoscroll ] = useCancelableTimeout( + () => setAutoscroll(true), + () => setAutoscroll(false), + TIMEOUT_DURATION, + ) + useEffect(() => { + if (autoscroll && autoscrollIndex !== savedIndex) { + updadteIndex(autoscrollIndex) + } + }, [ autoscroll, autoscrollIndex ]) + + const autoScrollRef = useLatestRef(autoscroll) + useEffect(() => { + if (!autoscroll) { + timeoutStartAutoscroll() + } + return () => { + if (autoScrollRef.current) { + updadteIndex(0) // index:0 means autoscroll is active + } + } + }, []) + + return { + autoscrollIndex, + timeoutStartAutoscroll, + stopAutoscroll, + } + +} \ No newline at end of file diff --git a/frontend/app/hooks/useCancelableTimeout.ts b/frontend/app/hooks/useCancelableTimeout.ts new file mode 100644 index 000000000..9f12f336a --- /dev/null +++ b/frontend/app/hooks/useCancelableTimeout.ts @@ -0,0 +1,20 @@ +import { useRef, useEffect } from 'react' + + +export default function useCancelableTimeout( + onTimeout: ()=> void, + onCancel: ()=> void, + delay: number, +): [()=> void, ()=> void] { + const idRef = useRef>() + const triggerTimeout = () => { + clearTimeout(idRef.current) + idRef.current = setTimeout(onTimeout, delay) + } + const cancelTimeout = () => { + clearTimeout(idRef.current) + onCancel() + } + useEffect(() => () => clearTimeout(idRef.current)) // auto-cancel without callback (clean) + return [ triggerTimeout, cancelTimeout ] +} \ No newline at end of file diff --git a/frontend/app/hooks/useLatestRef.ts b/frontend/app/hooks/useLatestRef.ts new file mode 100644 index 000000000..8dda027de --- /dev/null +++ b/frontend/app/hooks/useLatestRef.ts @@ -0,0 +1,8 @@ +import { useRef, useEffect } from 'react' + + +export default function useLatestRef(state: T) { + const ref = useRef(state) + useEffect(() => { ref.current = state }, [ state ]) + return ref +} \ No newline at end of file