1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Move all side effects to hooks

Signed-off-by: alexfront <alex.andreev.email@gmail.com>
This commit is contained in:
alexfront 2022-09-08 16:00:01 +03:00
parent ac8ed1478e
commit d951f3a017
6 changed files with 125 additions and 47 deletions

View File

@ -29,6 +29,14 @@
} }
} }
.firstLine {
width: 100%;
height: 1px;
top: 0;
background-color: var(--logsBackground);
position: absolute;
}
.lastLine { .lastLine {
width: 100%; width: 100%;
height: 1px; height: 1px;

View File

@ -5,18 +5,19 @@
import styles from "./log-list.module.scss"; import styles from "./log-list.module.scss";
import throttle from "lodash/throttle";
import { useVirtualizer } from '@tanstack/react-virtual'; import { useVirtualizer } from '@tanstack/react-virtual';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { useEffect, useRef } from 'react'; import React, { useRef } from 'react';
import type { LogTabViewModel } from './logs-view-model';
import { LogRow } from "./log-row";
import { cssNames } from "../../../utils"; import { cssNames } from "../../../utils";
import { v4 as getRandomId } from "uuid"; import { LogRow } from "./log-row";
import { useJumpToBottomButton } from "./use-scroll-to-bottom"; import type { LogTabViewModel } from './logs-view-model';
import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom";
import { ToBottom } from "./to-bottom"; import { ToBottom } from "./to-bottom";
import useIntersectionObserver from "../../../hooks/useIntersectionObserver"; import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom";
import { useOnScrollTop } from "./use-on-scroll-top";
import { useRefreshListOnDataChange } from "./use-refresh-list-on-data-change";
import { useScrollOnSearch } from "./use-scroll-on-search";
import { useJumpToBottomButton } from "./use-scroll-to-bottom";
import { useStickToBottomOnLogsLoad } from "./use-stick-to-bottom-on-logs-load";
export interface LogListProps { export interface LogListProps {
model: LogTabViewModel; model: LogTabViewModel;
@ -25,10 +26,9 @@ export interface LogListProps {
export const LogList = observer(({ model }: LogListProps) => { export const LogList = observer(({ model }: LogListProps) => {
const { visibleLogs } = model; const { visibleLogs } = model;
const parentRef = useRef<HTMLDivElement>(null); const parentRef = useRef<HTMLDivElement>(null);
const lastLineRef = useRef<HTMLDivElement>(null); const topLineRef = useRef<HTMLDivElement>(null);
const [rowKeySuffix, setRowKeySuffix] = React.useState(getRandomId()); const bottomLineRef = useRef<HTMLDivElement>(null);
const [toBottomVisible, setButtonVisibility] = useJumpToBottomButton(parentRef.current); const [toBottomVisible, setButtonVisibility] = useJumpToBottomButton(parentRef.current);
const entry = useIntersectionObserver(lastLineRef.current, {});
const rowVirtualizer = useVirtualizer({ const rowVirtualizer = useVirtualizer({
count: visibleLogs.get().length, count: visibleLogs.get().length,
@ -45,50 +45,21 @@ export const LogList = observer(({ model }: LogListProps) => {
scrollTo(visibleLogs.get().length - 1); scrollTo(visibleLogs.get().length - 1);
} }
const onScroll = throttle(() => { const onScroll = () => {
if (!parentRef.current) return; if (!parentRef.current) return;
setButtonVisibility(); setButtonVisibility();
onScrollToTop();
}, 1_000, { trailing: true, leading: true });
/**
* Loads new logs if user scrolled to the top
*/
const onScrollToTop = async () => {
const { scrollTop } = parentRef.current as HTMLDivElement;
if (scrollTop === 0) {
const logs = model.logs.get();
const firstLog = logs[0];
await model.loadLogs();
const scrollToIndex = model.logs.get().findIndex(log => log === firstLog);
scrollTo(scrollToIndex);
}
}; };
useInitialScrollToBottom(model, scrollToBottom); useInitialScrollToBottom(model, scrollToBottom);
useEffect(() => { const uniqRowKey = useRefreshListOnDataChange(model.logTabData.get());
// rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false });
// Refresh list
setRowKeySuffix(getRandomId());
}, [model.logTabData.get()]);
useEffect(() => { useScrollOnSearch(model.searchStore, scrollTo);
if (!model.searchStore.occurrences.length) return;
scrollTo(model.searchStore.occurrences[model.searchStore.activeOverlayIndex]); useStickToBottomOnLogsLoad({ bottomLineRef, model, scrollToBottom });
}, [model.searchStore.searchQuery, model.searchStore.activeOverlayIndex])
useEffect(() => { useOnScrollTop({ topLineRef, model, scrollTo });
if (entry?.isIntersecting) {
scrollToBottom();
}
}, [model.visibleLogs.get().length]);
return ( return (
<div <div
@ -102,9 +73,10 @@ export const LogList = observer(({ model }: LogListProps) => {
}} }}
className={styles.virtualizer} className={styles.virtualizer}
> >
<div className={styles.firstLine} ref={topLineRef}></div>
{rowVirtualizer.getVirtualItems().map((virtualRow) => ( {rowVirtualizer.getVirtualItems().map((virtualRow) => (
<div <div
key={virtualRow.index + rowKeySuffix} key={virtualRow.index + uniqRowKey}
ref={virtualRow.measureElement} ref={virtualRow.measureElement}
style={{ style={{
transform: `translateY(${virtualRow.start}px)`, transform: `translateY(${virtualRow.start}px)`,
@ -116,7 +88,7 @@ export const LogList = observer(({ model }: LogListProps) => {
</div> </div>
</div> </div>
))} ))}
<div className={styles.lastLine} ref={lastLineRef}></div> <div className={styles.lastLine} ref={bottomLineRef}></div>
</div> </div>
{toBottomVisible && ( {toBottomVisible && (
<ToBottom onClick={scrollToBottom} /> <ToBottom onClick={scrollToBottom} />

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RefObject } from "react";
import { useEffect } from "react";
import useIntersectionObserver from "../../../hooks/useIntersectionObserver";
import type { LogTabViewModel } from "./logs-view-model";
interface UseStickToBottomProps {
topLineRef: RefObject<HTMLDivElement>;
model: LogTabViewModel;
scrollTo: (index: number) => void;
}
export function useOnScrollTop({ topLineRef, model, scrollTo }: UseStickToBottomProps) {
const topLineEntry = useIntersectionObserver(topLineRef.current, {});
function getPreviouslyFirstLogIndex(firstLog: string) {
return model.logs.get().findIndex(log => log === firstLog);
}
async function onScrolledTop() {
const firstLog = model.logs.get()[0];
const scrollIndex = () => getPreviouslyFirstLogIndex(firstLog);
await model.loadLogs();
scrollTo(scrollIndex());
}
useEffect(() => {
if (topLineEntry?.isIntersecting) {
onScrolledTop();
}
}, [topLineEntry?.isIntersecting]);
}

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { useEffect, useState } from "react";
import type { LogTabData } from "./tab-store";
import { v4 as getRandomId } from "uuid";
export function useRefreshListOnDataChange(data: LogTabData | undefined) {
const [rowKeySuffix, setRowKeySuffix] = useState(getRandomId());
useEffect(() => {
// Refresh virtualizer list rows by changing their keys
setRowKeySuffix(getRandomId());
}, [data]);
return rowKeySuffix;
}

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { useEffect } from "react";
import type { SearchStore } from "../../../search-store/search-store";
export function useScrollOnSearch(store: SearchStore, scrollTo: (index: number) => void) {
const { occurrences, searchQuery, activeOverlayIndex } = store;
useEffect(() => {
if (!occurrences.length) return;
scrollTo(occurrences[activeOverlayIndex]);
}, [searchQuery, activeOverlayIndex]);
}

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RefObject } from "react";
import { useEffect } from "react";
import useIntersectionObserver from "../../../hooks/useIntersectionObserver";
import type { LogTabViewModel } from "./logs-view-model";
interface UseStickToBottomProps {
bottomLineRef: RefObject<HTMLDivElement>;
model: LogTabViewModel;
scrollToBottom: () => void;
}
export function useStickToBottomOnLogsLoad({ bottomLineRef, model, scrollToBottom }: UseStickToBottomProps) {
const bottomLineEntry = useIntersectionObserver(bottomLineRef.current, {});
useEffect(() => {
if (bottomLineEntry?.isIntersecting) {
scrollToBottom();
}
}, [model.visibleLogs.get().length]);
}