diff --git a/src/app/features/inspector/hooks/scroll-position.ts b/src/app/features/inspector/hooks/scroll-position.ts new file mode 100644 index 0000000000..5c3fe85264 --- /dev/null +++ b/src/app/features/inspector/hooks/scroll-position.ts @@ -0,0 +1,21 @@ +import {MutableRefObject, useEffect} from "react" +import {InspectorProps} from "../types" + +export function useInitialScrollPosition( + ref: MutableRefObject, + props: InspectorProps +) { + useEffect(() => { + const pos = props.initialScrollPosition + const el = ref.current + let id + if (pos && el) { + el.scrollTop = pos.top + // First scroll down so that the rows can render, then scroll to the right + id = setTimeout(() => { + el.scrollLeft = pos.left + }) + } + return () => clearTimeout(id) + }, []) +} diff --git a/src/app/features/inspector/hooks/scroll.ts b/src/app/features/inspector/hooks/scroll.ts new file mode 100644 index 0000000000..f4e251539c --- /dev/null +++ b/src/app/features/inspector/hooks/scroll.ts @@ -0,0 +1,18 @@ +import {MutableRefObject} from "react" +import useListener from "src/js/components/hooks/useListener" +import {InspectorProps} from "../types" + +export function useOnScroll( + ref: MutableRefObject, + props: InspectorProps +) { + const onScroll = () => { + if (props.onScroll && ref.current) { + const top = ref.current.scrollTop + const left = ref.current.scrollLeft + props.onScroll({top, left}) + } + } + + useListener(ref.current, "scroll", onScroll) +} diff --git a/src/app/features/inspector/inspector.tsx b/src/app/features/inspector/inspector.tsx index 22c576a5d2..29149c3e95 100644 --- a/src/app/features/inspector/inspector.tsx +++ b/src/app/features/inspector/inspector.tsx @@ -1,17 +1,24 @@ -import React, {useMemo, useState} from "react" +import React, {useMemo, useRef, useState} from "react" +import {useOnScroll} from "./hooks/scroll" +import {useInitialScrollPosition} from "./hooks/scroll-position" import {InspectList} from "./inspect-list" import {List} from "./list.styled" import {Row} from "./row" import {InspectorProps} from "./types" export function Inspector(props: InspectorProps) { + const outerRef = useRef() const [visibleRange, setVisibleRange] = useState([0, 30] as [number, number]) const list = useMemo(() => new InspectList(props), [props]) list.fill(visibleRange) + useOnScroll(outerRef, props) + useInitialScrollPosition(outerRef, props) + return ( + {view.openToken()} ) @@ -14,7 +17,10 @@ export function open(view: ContainerView) { export function close(view: ContainerView) { return ( - + {view.closeToken()} ) @@ -39,7 +45,7 @@ export function anchor(view: ContainerView, children: ReactElement[]) { export function name(view: ContainerView) { return ( + return } diff --git a/src/app/features/inspector/types.ts b/src/app/features/inspector/types.ts index a6c4b2fa2e..c4836e73ae 100644 --- a/src/app/features/inspector/types.ts +++ b/src/app/features/inspector/types.ts @@ -22,6 +22,8 @@ export type InspectorProps = { onClick?: InspectorMouseEvent loadMore?: Function innerRef?: React.Ref + onScroll?: (props: {top: number; left: number}) => void + initialScrollPosition?: {top: number; left: number} } export type InspectArgs = { diff --git a/src/app/query-home/results/main-inspector.tsx b/src/app/query-home/results/main-inspector.tsx index 3c8a945788..f321a1309b 100644 --- a/src/app/query-home/results/main-inspector.tsx +++ b/src/app/query-home/results/main-inspector.tsx @@ -2,13 +2,14 @@ import {zed} from "@brimdata/zealot" import useSelect from "src/app/core/hooks/use-select" import {Inspector} from "src/app/features/inspector/inspector" import searchFieldContextMenu from "src/ppl/menus/searchFieldContextMenu" -import React, {useCallback, MouseEvent} from "react" +import React, {useCallback, MouseEvent, useMemo} from "react" import {useDispatch, useSelector} from "react-redux" import {viewLogDetail} from "src/js/flows/viewLogDetail" import Slice from "src/js/state/Inspector" import Viewer from "src/js/state/Viewer" import nextPageViewerSearch from "../flows/next-page-viewer-search" import {useRowSelection} from "./results-table/hooks/use-row-selection" +import {debounce} from "lodash" export function MainInspector(props: { height: number @@ -62,8 +63,24 @@ export function MainInspector(props: { clicked(e, index) } + function onScroll({top, left}) { + dispatch(Slice.setScrollPosition({top, left})) + } + + const safeOnScroll = useMemo( + () => debounce(onScroll, 250, {trailing: true, leading: false}), + [] + ) + + const initialScrollPosition = useMemo( + () => select(Slice.getScrollPosition), + [] + ) + return ( { ) } + function onScroll({top, left}) { + dispatch(Viewer.setScroll({y: top, x: left})) + } + + const safeOnScroll = useMemo( + () => debounce(onScroll, 250, {trailing: true, leading: false}), + [] + ) + if (isEmpty(logs) && isFetching) return null if (isEmpty(logs)) return return ( any scrollPos: ScrollPosition innerRef: any + onScroll?: (props: {left: number; top: number}) => void } const Viewer = (props: Props) => { @@ -44,6 +45,9 @@ const Viewer = (props: Props) => { scrollHooks && scrollHooks() const view = ref.current if (view) { + const top = view.scrollTop + const left = view.scrollLeft + props.onScroll({top, left}) updateChunks(view.scrollTop) setScrollLeft(view.scrollLeft) } diff --git a/src/app/routes/search/main-inspector.tsx b/src/app/routes/search/main-inspector.tsx index 2ba7d5a9de..ef2258f651 100644 --- a/src/app/routes/search/main-inspector.tsx +++ b/src/app/routes/search/main-inspector.tsx @@ -3,12 +3,13 @@ import useSelect from "src/app/core/hooks/use-select" import {Inspector} from "src/app/features/inspector/inspector" import nextPageViewerSearch from "src/app/search/flows/next-page-viewer-search" import searchFieldContextMenu from "src/ppl/menus/searchFieldContextMenu" -import React, {MouseEvent, useCallback} from "react" +import React, {MouseEvent, useCallback, useMemo} from "react" import {useDispatch, useSelector} from "react-redux" import {useRowSelection} from "src/js/components/SearchResults/selection" import {viewLogDetail} from "src/js/flows/viewLogDetail" import Slice from "src/js/state/Inspector" import Viewer from "src/js/state/Viewer" +import {debounce} from "lodash" export function MainInspector(props: { height: number @@ -62,8 +63,24 @@ export function MainInspector(props: { clicked(e, index) } + function onScroll({top, left}) { + dispatch(Slice.setScrollPosition({top, left})) + } + + const safeOnScroll = useMemo( + () => debounce(onScroll, 250, {trailing: true, leading: false}), + [] + ) + + const initialScrollPosition = useMemo( + () => select(Slice.getScrollPosition), + [] + ) + return ( debounce(onScroll, 250, {trailing: true, leading: false}), + [] + ) + if (isEmpty(logs) && isFetching) return null if (isEmpty(logs)) return return ( any scrollPos: ScrollPosition + onScroll?: (props: {left: number; top: number}) => void innerRef: any } @@ -44,6 +45,9 @@ export default function Viewer(props: Props) { scrollHooks && scrollHooks() const view = ref.current if (view) { + const top = view.scrollTop + const left = view.scrollLeft + props.onScroll({top, left}) updateChunks(view.scrollTop) setScrollLeft(view.scrollLeft) } diff --git a/src/js/components/hooks/useEventListener.ts b/src/js/components/hooks/useEventListener.ts index 1f38a947df..ac73be6649 100644 --- a/src/js/components/hooks/useEventListener.ts +++ b/src/js/components/hooks/useEventListener.ts @@ -1,5 +1,6 @@ import {useEffect} from "react" +// DO NOT USE use useListener instead export default function useEventListener( el: EventTarget, name: string, diff --git a/src/js/state/Inspector/index.ts b/src/js/state/Inspector/index.ts index 99f450d301..1da30d4ffd 100644 --- a/src/js/state/Inspector/index.ts +++ b/src/js/state/Inspector/index.ts @@ -4,5 +4,6 @@ import {actions} from "./reducer" export default { getExpanded: activeTabSelect((t) => t.inspector.expanded), getDefaultExpanded: activeTabSelect((t) => t.inspector.defaultExpanded), + getScrollPosition: activeTabSelect((t) => t.inspector.scrollPosition), ...actions } diff --git a/src/js/state/Inspector/reducer.ts b/src/js/state/Inspector/reducer.ts index d8135674c0..725c904ac0 100644 --- a/src/js/state/Inspector/reducer.ts +++ b/src/js/state/Inspector/reducer.ts @@ -5,15 +5,11 @@ const slice = createSlice({ name: "TAB_INSPECTOR", initialState: { rows: [] as RowData[], - scrollTop: 0, - maxVisibleRowIndex: 0, - expanded: new Map(), - defaultExpanded: false + expanded: new Map(), + defaultExpanded: false, + scrollPosition: {top: 0, left: 0} }, reducers: { - setMaxVisibleRowIndex: (s, a: PayloadAction) => { - s.maxVisibleRowIndex = a.payload - }, appendRows: (s, a: PayloadAction) => { s.rows = s.rows.concat(a.payload) }, @@ -29,16 +25,14 @@ const slice = createSlice({ s.defaultExpanded = a.payload s.rows = [] }, - setScrollTop: (s, a: PayloadAction) => { - s.scrollTop = a.payload + setScrollPosition: (s, a: PayloadAction<{top: number; left: number}>) => { + s.scrollPosition = a.payload } }, extraReducers: (builder) => { builder.addCase("VIEWER_CLEAR", (s) => { s.rows = [] - s.maxVisibleRowIndex = 0 - s.scrollTop = 0 - s.expanded = new Map() + s.expanded = new Map() }) } })