Skip to content

Commit

Permalink
Save scroll position (#2271)
Browse files Browse the repository at this point in the history
The scroll position is now saved when you switch back and forth between the inspector and the viewer.
  • Loading branch information
jameskerr authored Mar 14, 2022
1 parent c24c5c5 commit 62214f3
Show file tree
Hide file tree
Showing 16 changed files with 134 additions and 22 deletions.
21 changes: 21 additions & 0 deletions src/app/features/inspector/hooks/scroll-position.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {MutableRefObject, useEffect} from "react"
import {InspectorProps} from "../types"

export function useInitialScrollPosition(
ref: MutableRefObject<HTMLDivElement>,
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)
}, [])
}
18 changes: 18 additions & 0 deletions src/app/features/inspector/hooks/scroll.ts
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>,
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)
}
9 changes: 8 additions & 1 deletion src/app/features/inspector/inspector.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement>()
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 (
<List
innerRef={props.innerRef}
outerRef={outerRef}
height={props.height}
width={props.width}
itemCount={list.count}
Expand Down
12 changes: 9 additions & 3 deletions src/app/features/inspector/templates/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ import {zed} from "@brimdata/zealot"

export function open(view: ContainerView) {
return (
<span key="open-token" className="zed-syntax">
<span
key={"open-token-" + view.args.indexPath.join(",")}
className="zed-syntax"
>
{view.openToken()}
</span>
)
}

export function close(view: ContainerView) {
return (
<span key="close-token" className="zed-syntax">
<span
key={"close-token-" + view.args.indexPath.join(",")}
className="zed-syntax"
>
{view.closeToken()}
</span>
)
Expand All @@ -39,7 +45,7 @@ export function anchor(view: ContainerView, children: ReactElement[]) {
export function name(view: ContainerView) {
return (
<span
key="name"
key={"view" + view.name()}
className={classNames("zed-container", {
"zed-type": zed.isType(view.args.value),
"zed-error": view.args.value instanceof zed.Error
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/inspector/templates/space.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react"

export function space() {
return <span> </span>
return <span key="space"> </span>
}
2 changes: 2 additions & 0 deletions src/app/features/inspector/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type InspectorProps = {
onClick?: InspectorMouseEvent
loadMore?: Function
innerRef?: React.Ref<any>
onScroll?: (props: {top: number; left: number}) => void
initialScrollPosition?: {top: number; left: number}
}

export type InspectArgs = {
Expand Down
19 changes: 18 additions & 1 deletion src/app/query-home/results/main-inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (
<Inspector
initialScrollPosition={initialScrollPosition}
onScroll={safeOnScroll}
innerRef={parentRef}
isExpanded={useCallback(isExpanded, [expanded, defaultExpanded])}
setExpanded={useCallback(setExpanded, [])}
Expand Down
12 changes: 11 additions & 1 deletion src/app/query-home/results/results-table/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isEmpty} from "lodash"
import {debounce, isEmpty} from "lodash"
import React, {useEffect, useMemo} from "react"
import {useDispatch, useSelector} from "react-redux"
import ConfigPropValues from "src/js/state/ConfigPropValues"
Expand Down Expand Up @@ -107,11 +107,21 @@ const ResultsTable = (props: Props) => {
)
}

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 <NoResults width={props.width} />

return (
<ViewerComponent
onScroll={safeOnScroll}
innerRef={parentRef}
logs={logs}
renderRow={renderRow}
Expand Down
4 changes: 4 additions & 0 deletions src/app/query-home/results/results-table/viewer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Props = {
renderEnd: () => any
scrollPos: ScrollPosition
innerRef: any
onScroll?: (props: {left: number; top: number}) => void
}

const Viewer = (props: Props) => {
Expand All @@ -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)
}
Expand Down
19 changes: 18 additions & 1 deletion src/app/routes/search/main-inspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (
<Inspector
initialScrollPosition={initialScrollPosition}
onScroll={safeOnScroll}
innerRef={parentRef}
isExpanded={useCallback(isExpanded, [expanded, defaultExpanded])}
setExpanded={useCallback(setExpanded, [])}
Expand Down
2 changes: 1 addition & 1 deletion src/css/settings/_fonts.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
src: url(../static/fonts/Recursive.woff2) format("woff2-variations");
font-style: oblique 0deg 15deg;
font-weight: 300 1000;
font-display: swap;
font-display: block;
}

$heading-font: system-ui, sans-serif;
Expand Down
12 changes: 11 additions & 1 deletion src/js/components/SearchResults/ResultsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import nextPageViewerSearch from "src/app/search/flows/next-page-viewer-search"
import {isEmpty} from "lodash"
import {debounce, isEmpty} from "lodash"
import React, {useEffect, useMemo} from "react"
import {useDispatch, useSelector} from "react-redux"
import ConfigPropValues from "src/js/state/ConfigPropValues"
Expand Down Expand Up @@ -107,11 +107,21 @@ export default function ResultsTable(props: Props) {
)
}

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 <NoResults width={props.width} />

return (
<ViewerComponent
onScroll={safeOnScroll}
innerRef={parentRef}
logs={logs}
renderRow={renderRow}
Expand Down
4 changes: 4 additions & 0 deletions src/js/components/Viewer/Viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Props = {
onLastChunk?: Function
renderEnd: () => any
scrollPos: ScrollPosition
onScroll?: (props: {left: number; top: number}) => void
innerRef: any
}

Expand All @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions src/js/components/hooks/useEventListener.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {useEffect} from "react"

// DO NOT USE use useListener instead
export default function useEventListener(
el: EventTarget,
name: string,
Expand Down
1 change: 1 addition & 0 deletions src/js/state/Inspector/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
18 changes: 6 additions & 12 deletions src/js/state/Inspector/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ const slice = createSlice({
name: "TAB_INSPECTOR",
initialState: {
rows: [] as RowData[],
scrollTop: 0,
maxVisibleRowIndex: 0,
expanded: new Map<any, any>(),
defaultExpanded: false
expanded: new Map<string, boolean>(),
defaultExpanded: false,
scrollPosition: {top: 0, left: 0}
},
reducers: {
setMaxVisibleRowIndex: (s, a: PayloadAction<number>) => {
s.maxVisibleRowIndex = a.payload
},
appendRows: (s, a: PayloadAction<RowData[]>) => {
s.rows = s.rows.concat(a.payload)
},
Expand All @@ -29,16 +25,14 @@ const slice = createSlice({
s.defaultExpanded = a.payload
s.rows = []
},
setScrollTop: (s, a: PayloadAction<number>) => {
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<any, any>()
s.expanded = new Map<string, boolean>()
})
}
})
Expand Down

0 comments on commit 62214f3

Please sign in to comment.