From da39984cc17f651379b33c97a75e92ae75c058d7 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 29 Aug 2022 15:53:07 +0300 Subject: [PATCH 01/73] Create functional LogList component Signed-off-by: Alex Andreev --- .../components/dock/logs/log-list.tsx | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/renderer/components/dock/logs/log-list.tsx diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx new file mode 100644 index 0000000000..56202e0676 --- /dev/null +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -0,0 +1,108 @@ +import { useVirtualizer } from '@tanstack/react-virtual'; +import AnsiUp from 'ansi_up'; +import DOMPurify from 'dompurify'; +import React, { useRef } from 'react'; +import { SearchStore } from '../../../search-store/search-store'; +import { cssNames } from '../../../utils'; +import type { LogTabViewModel } from './logs-view-model'; + +export interface LogListProps { + model: LogTabViewModel; +} + +export const LogList = ({ model }: LogListProps) => { + const { logs } = model; + const parentRef = useRef(null) + const rowVirtualizer = useVirtualizer({ + count: logs.get().length, + getScrollElement: () => parentRef.current, + estimateSize: () => 38, + overscan: 5 + }) + + return ( +
+
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => ( +
+
+ +
+
+ ))} +
+
+ ) +} + +const colorConverter = new AnsiUp(); + +function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabViewModel }) { + const { searchQuery, isActiveOverlay } = model.searchStore; + const log = model.logs.get()[rowIndex]; + const contents: React.ReactElement[] = []; + const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); + + if (searchQuery) { // If search is enabled, replace keyword with backgrounded + // Case-insensitive search (lowercasing query and keywords in line) + const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi"); + const matches = log.matchAll(regex); + const modified = log.replace(regex, match => match.toLowerCase()); + // Splitting text line by keyword + const pieces = modified.split(searchQuery.toLowerCase()); + + pieces.forEach((piece, index) => { + const active = isActiveOverlay(rowIndex, index); + const lastItem = index === pieces.length - 1; + const overlayValue = matches.next().value; + const overlay = !lastItem + ? ( + + ) + : null; + + contents.push( + + + {overlay} + , + ); + }); + } + + return ( +
+ {contents.length > 1 ? contents : ( + + )} + {/* For preserving copy-paste experience and keeping line breaks */} +
+
+ ); +} \ No newline at end of file From d51c327370e31d4fb07eee143d0cfb8cb31a8d76 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 29 Aug 2022 15:54:01 +0300 Subject: [PATCH 02/73] Add tanstack/react-virtual dependency Signed-off-by: Alex Andreev --- package.json | 1 + yarn.lock | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/package.json b/package.json index 523e78c75b..880ef6ab5c 100644 --- a/package.json +++ b/package.json @@ -226,6 +226,7 @@ "@sentry/electron": "^3.0.7", "@sentry/integrations": "^6.19.3", "@side/jest-runtime": "^1.0.1", + "@tanstack/react-virtual": "3.0.0-beta.18", "@types/circular-dependency-plugin": "5.0.5", "abort-controller": "^3.0.0", "auto-bind": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index bd1ba7712e..ec008d2905 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1582,6 +1582,18 @@ dependencies: defer-to-connect "^2.0.0" +"@tanstack/react-virtual@3.0.0-beta.18": + version "3.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.18.tgz#b97b2019f7d6a5770fb88ee1f7591da55b9059b4" + integrity sha512-mnyCZT6htcRNw1jVb+WyfMUMbd1UmXX/JWPuMf6Bmj92DB/V7Ogk5n5rby5Y5aste7c7mlsBeMF8HtpwERRvEQ== + dependencies: + "@tanstack/virtual-core" "3.0.0-beta.18" + +"@tanstack/virtual-core@3.0.0-beta.18": + version "3.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.18.tgz#d4b0738c1d0aada922063c899675ff4df9f696b2" + integrity sha512-tcXutY05NpN9lp3+AXI9Sn85RxSPV0EJC0XMim9oeQj/E7bjXoL0qZ4Er4wwnvIbv/hZjC91EmbIQGjgdr6nZg== + "@testing-library/dom@>=7", "@testing-library/dom@^8.0.0": version "8.13.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.13.0.tgz#bc00bdd64c7d8b40841e27a70211399ad3af46f5" From 6f91071af4cd74d0dea1845217b57fb249f32d94 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Wed, 31 Aug 2022 14:34:54 +0300 Subject: [PATCH 03/73] Using new Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 7 ++++--- src/renderer/components/dock/logs/view.tsx | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 56202e0676..00c1c22d90 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -1,6 +1,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import AnsiUp from 'ansi_up'; import DOMPurify from 'dompurify'; +import { observer } from 'mobx-react'; import React, { useRef } from 'react'; import { SearchStore } from '../../../search-store/search-store'; import { cssNames } from '../../../utils'; @@ -10,7 +11,7 @@ export interface LogListProps { model: LogTabViewModel; } -export const LogList = ({ model }: LogListProps) => { +export const LogList = observer(({ model }: LogListProps) => { const { logs } = model; const parentRef = useRef(null) const rowVirtualizer = useVirtualizer({ @@ -24,7 +25,7 @@ export const LogList = ({ model }: LogListProps) => {
@@ -56,7 +57,7 @@ export const LogList = ({ model }: LogListProps) => {
) -} +}); const colorConverter = new AnsiUp(); diff --git a/src/renderer/components/dock/logs/view.tsx b/src/renderer/components/dock/logs/view.tsx index 157ccfa778..19d1b84691 100644 --- a/src/renderer/components/dock/logs/view.tsx +++ b/src/renderer/components/dock/logs/view.tsx @@ -8,7 +8,7 @@ import { observer } from "mobx-react"; import { InfoPanel } from "../info-panel"; import { LogResourceSelector } from "./resource-selector"; import type { LogListRef } from "./list"; -import { LogList } from "./list"; +// import { LogList } from "./list"; import { LogSearch } from "./search"; import { LogControls } from "./controls"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -20,6 +20,8 @@ import type { SubscribeStores } from "../../../kube-watch-api/kube-watch-api"; import subscribeStoresInjectable from "../../../kube-watch-api/subscribe-stores.injectable"; import type { PodStore } from "../../+workloads-pods/store"; import podStoreInjectable from "../../+workloads-pods/store.injectable"; +import { noop } from "lodash"; +import { LogList } from "./log-list"; export interface LogsDockTabProps { className?: string; @@ -83,7 +85,7 @@ const NonInjectedLogsDockTab = observer(({ )} @@ -91,7 +93,8 @@ const NonInjectedLogsDockTab = observer(({ showButtons={false} showStatusPanel={false} /> - + {/* */} + ); From 843c0f76f6ea640a82084bf95d73e7639728e20e Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 1 Sep 2022 09:27:55 +0300 Subject: [PATCH 04/73] Remove overflow from the LogControls Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/controls.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/renderer/components/dock/logs/controls.scss b/src/renderer/components/dock/logs/controls.scss index 1a5c3f751c..607359bf3c 100644 --- a/src/renderer/components/dock/logs/controls.scss +++ b/src/renderer/components/dock/logs/controls.scss @@ -4,8 +4,6 @@ */ .LogControls { - @include hidden-scrollbar; - background: var(--dockInfoBackground); padding: $padding $padding * 2; } From 85fadc74e0a10b3b8c778a1011fa658c95d1fc7b Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 1 Sep 2022 09:28:31 +0300 Subject: [PATCH 05/73] Set some list onScroll events Signed-off-by: Alex Andreev --- .../components/dock/logs/log-list.tsx | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 00c1c22d90..c2a84ce7b0 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -2,7 +2,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import AnsiUp from 'ansi_up'; import DOMPurify from 'dompurify'; import { observer } from 'mobx-react'; -import React, { useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { SearchStore } from '../../../search-store/search-store'; import { cssNames } from '../../../utils'; import type { LogTabViewModel } from './logs-view-model'; @@ -12,14 +12,55 @@ export interface LogListProps { } export const LogList = observer(({ model }: LogListProps) => { + const [toBottomVisible, setToBottomVisible] = React.useState(false); + const [lastLineVisible, setLastLineVisible] = React.useState(true); + const { logs } = model; const parentRef = useRef(null) const rowVirtualizer = useVirtualizer({ count: logs.get().length, getScrollElement: () => parentRef.current, estimateSize: () => 38, - overscan: 5 - }) + overscan: 5, + scrollPaddingEnd: 0, + scrollPaddingStart: 0, + }); + + const onScroll = (event: React.UIEvent) => { + console.log("scrolling", toBottomVisible) + if (!parentRef.current) return; + + setToBottomVisibility(); + setLastLineVisibility(); + } + + // TODO: Move to its own hook + const setToBottomVisibility = () => { + const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; + console.log("scrolling", scrollHeight, scrollTop, rowVirtualizer.getTotalSize()) + if (scrollHeight - scrollTop > 4000) { + setToBottomVisible(true); + } else { + setToBottomVisible(false); + } + } + + const setLastLineVisibility = () => { + const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; + + if (scrollHeight - scrollTop < 4000) { + setLastLineVisible(true); + } else { + setLastLineVisible(false); + } + } + + useEffect(() => { + setTimeout(() => { + // Initial scroll to bottom + rowVirtualizer.scrollToIndex(logs.get().length - 1, { align: 'end', smoothScroll: false }); + }, 200) + }, []) return (
{ flexGrow: 1, overflow: 'auto', // Make it scroll! }} + onScroll={onScroll} >
{
{
))} +
) From 88678f63a212ce5aeec5bdf14b590b1ebe871a23 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 1 Sep 2022 09:44:19 +0300 Subject: [PATCH 06/73] Load more logs when scrolled to top Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index c2a84ce7b0..61d2fa85cd 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -32,6 +32,7 @@ export const LogList = observer(({ model }: LogListProps) => { setToBottomVisibility(); setLastLineVisibility(); + checkLoadIntent(); } // TODO: Move to its own hook @@ -55,6 +56,17 @@ export const LogList = observer(({ model }: LogListProps) => { } } + /** + * Check if user scrolled to top and new logs should be loaded + */ + const checkLoadIntent = () => { + const { scrollTop } = parentRef.current as HTMLDivElement; + + if (scrollTop === 0) { + model.loadLogs(); + } + }; + useEffect(() => { setTimeout(() => { // Initial scroll to bottom From e16fcf23a707e4032d20612556f8c962841688c4 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 1 Sep 2022 09:51:06 +0300 Subject: [PATCH 07/73] Remove overflow from the LogControls Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/controls.module.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/renderer/components/dock/logs/controls.module.scss b/src/renderer/components/dock/logs/controls.module.scss index 27eefd3dab..516109426b 100644 --- a/src/renderer/components/dock/logs/controls.module.scss +++ b/src/renderer/components/dock/logs/controls.module.scss @@ -1,6 +1,4 @@ .controls { - @include hidden-scrollbar; - display: flex; gap: var(--padding); align-items: center; From af8f39f83a6272150382d026db32e83fca3e18ec Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Thu, 1 Sep 2022 11:28:09 +0300 Subject: [PATCH 08/73] Show logs regarding to showTimestamps flag Signed-off-by: Alex Andreev --- .../dock/logs/get-visible-logs.injectable.ts | 38 +++++++++++++++++++ .../components/dock/logs/log-list.tsx | 8 ++-- .../dock/logs/logs-view-model.injectable.ts | 2 + .../components/dock/logs/logs-view-model.ts | 2 + 4 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 src/renderer/components/dock/logs/get-visible-logs.injectable.ts diff --git a/src/renderer/components/dock/logs/get-visible-logs.injectable.ts b/src/renderer/components/dock/logs/get-visible-logs.injectable.ts new file mode 100644 index 0000000000..debd13f84a --- /dev/null +++ b/src/renderer/components/dock/logs/get-visible-logs.injectable.ts @@ -0,0 +1,38 @@ +import { getInjectable } from "@ogre-tools/injectable"; +import moment from "moment"; +import userStoreInjectable from "../../../../common/user-store/user-store.injectable"; +import type { TabId } from "../dock/store"; +import getLogTabDataInjectable from "./get-log-tab-data.injectable"; +import getLogsWithoutTimestampsInjectable from "./get-logs-without-timestamps.injectable"; +import getTimestampSplitLogsInjectable from "./get-timestamp-split-logs.injectable"; + +const getVisibleLogsInjectable = getInjectable({ + id: "get-visible-logs", + + instantiate: (di) => { + return (tabId: TabId) => { + const getLogTabData = di.inject(getLogTabDataInjectable); + const getTimestampSplitLogs = di.inject(getTimestampSplitLogsInjectable); + const userStore = di.inject(userStoreInjectable) + const logTabData = getLogTabData(tabId); + + if (!logTabData) { + return []; + } + + const { showTimestamps } = logTabData; + + if (!showTimestamps) { + const getLogsWithoutTimestamps = di.inject(getLogsWithoutTimestampsInjectable); + + return getLogsWithoutTimestamps(tabId); + } + + return getTimestampSplitLogs(tabId).map(([logTimestamp, log]) => ( + `${logTimestamp && moment.tz(logTimestamp, userStore.localeTimezone).format()}${log}` + )); + } + } +}); + +export default getVisibleLogsInjectable; \ No newline at end of file diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 61d2fa85cd..9a30bac0b4 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -15,10 +15,10 @@ export const LogList = observer(({ model }: LogListProps) => { const [toBottomVisible, setToBottomVisible] = React.useState(false); const [lastLineVisible, setLastLineVisible] = React.useState(true); - const { logs } = model; + const { visibleLogs } = model; const parentRef = useRef(null) const rowVirtualizer = useVirtualizer({ - count: logs.get().length, + count: visibleLogs.get().length, getScrollElement: () => parentRef.current, estimateSize: () => 38, overscan: 5, @@ -70,7 +70,7 @@ export const LogList = observer(({ model }: LogListProps) => { useEffect(() => { setTimeout(() => { // Initial scroll to bottom - rowVirtualizer.scrollToIndex(logs.get().length - 1, { align: 'end', smoothScroll: false }); + rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); }, 200) }, []) @@ -123,7 +123,7 @@ const colorConverter = new AnsiUp(); function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabViewModel }) { const { searchQuery, isActiveOverlay } = model.searchStore; - const log = model.logs.get()[rowIndex]; + const log = model.visibleLogs.get()[rowIndex]; const contents: React.ReactElement[] = []; const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); diff --git a/src/renderer/components/dock/logs/logs-view-model.injectable.ts b/src/renderer/components/dock/logs/logs-view-model.injectable.ts index 77e4ca983c..a783db1032 100644 --- a/src/renderer/components/dock/logs/logs-view-model.injectable.ts +++ b/src/renderer/components/dock/logs/logs-view-model.injectable.ts @@ -20,6 +20,7 @@ import getPodsByOwnerIdInjectable from "../../+workloads-pods/get-pods-by-owner- import getPodByIdInjectable from "../../+workloads-pods/get-pod-by-id.injectable"; import downloadLogsInjectable from "./download-logs.injectable"; import downloadAllLogsInjectable from "./download-all-logs.injectable"; +import getVisibleLogsInjectable from "./get-visible-logs.injectable"; export interface InstantiateArgs { tabId: TabId; @@ -32,6 +33,7 @@ const logsViewModelInjectable = getInjectable({ getLogs: di.inject(getLogsInjectable), getLogsWithoutTimestamps: di.inject(getLogsWithoutTimestampsInjectable), getTimestampSplitLogs: di.inject(getTimestampSplitLogsInjectable), + getVisibleLogs: di.inject(getVisibleLogsInjectable), reloadLogs: di.inject(reloadLogsInjectable), getLogTabData: di.inject(getLogTabDataInjectable), setLogTabData: di.inject(setLogTabDataInjectable), diff --git a/src/renderer/components/dock/logs/logs-view-model.ts b/src/renderer/components/dock/logs/logs-view-model.ts index 85b8502df3..b32fd340e3 100644 --- a/src/renderer/components/dock/logs/logs-view-model.ts +++ b/src/renderer/components/dock/logs/logs-view-model.ts @@ -19,6 +19,7 @@ export interface LogTabViewModelDependencies { getLogs: (tabId: TabId) => string[]; getLogsWithoutTimestamps: (tabId: TabId) => string[]; getTimestampSplitLogs: (tabId: TabId) => [string, string][]; + getVisibleLogs: (tabId: TabId) => string[]; getLogTabData: (tabId: TabId) => LogTabData | undefined; setLogTabData: (tabId: TabId, data: LogTabData) => void; loadLogs: LoadLogs; @@ -45,6 +46,7 @@ export class LogTabViewModel { readonly logsWithoutTimestamps = computed(() => this.dependencies.getLogsWithoutTimestamps(this.tabId)); readonly timestampSplitLogs = computed(() => this.dependencies.getTimestampSplitLogs(this.tabId)); readonly logTabData = computed(() => this.dependencies.getLogTabData(this.tabId)); + readonly visibleLogs = computed(() => this.dependencies.getVisibleLogs(this.tabId)); readonly pods = computed(() => { const data = this.logTabData.get(); From 62cc0d768979e7e91c82fe092ca53343a1083b74 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 2 Sep 2022 10:28:37 +0300 Subject: [PATCH 09/73] Keep scrolling position when loaded more logs Signed-off-by: Alex Andreev --- .../components/dock/logs/log-list.tsx | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 9a30bac0b4..bba350d8ad 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -27,18 +27,17 @@ export const LogList = observer(({ model }: LogListProps) => { }); const onScroll = (event: React.UIEvent) => { - console.log("scrolling", toBottomVisible) if (!parentRef.current) return; setToBottomVisibility(); setLastLineVisibility(); - checkLoadIntent(); + onScrollToTop(); } // TODO: Move to its own hook const setToBottomVisibility = () => { const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; - console.log("scrolling", scrollHeight, scrollTop, rowVirtualizer.getTotalSize()) + // console.log("scrolling", scrollHeight, scrollTop, rowVirtualizer.getTotalSize()) if (scrollHeight - scrollTop > 4000) { setToBottomVisible(true); } else { @@ -59,11 +58,23 @@ export const LogList = observer(({ model }: LogListProps) => { /** * Check if user scrolled to top and new logs should be loaded */ - const checkLoadIntent = () => { + const onScrollToTop = async () => { const { scrollTop } = parentRef.current as HTMLDivElement; if (scrollTop === 0) { - model.loadLogs(); + const oldLogsAmount = visibleLogs.get().length; + await model.loadLogs(); + const newLogsAmount = visibleLogs.get().length; + + + const scrollToIndex = newLogsAmount - oldLogsAmount; + console.log("new logs loaded", oldLogsAmount, newLogsAmount, scrollToIndex); + + setTimeout(() => { + rowVirtualizer.scrollToIndex(scrollToIndex, { align: 'start', smoothScroll: false }); + }, 1000) + + // rowVirtualizer.scrollToIndex(scrollToIndex, { align: 'start', smoothScroll: false }); } }; From 968621f7c6114b42848bb6863b687e3cd52b4d7e Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 2 Sep 2022 13:46:44 +0300 Subject: [PATCH 10/73] Fix scroll position when more logs loaded Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index bba350d8ad..03a34da5a4 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -62,19 +62,14 @@ export const LogList = observer(({ model }: LogListProps) => { const { scrollTop } = parentRef.current as HTMLDivElement; if (scrollTop === 0) { - const oldLogsAmount = visibleLogs.get().length; + const logs = model.logs.get(); + const firstLog = logs[0]; + await model.loadLogs(); - const newLogsAmount = visibleLogs.get().length; - - const scrollToIndex = newLogsAmount - oldLogsAmount; - console.log("new logs loaded", oldLogsAmount, newLogsAmount, scrollToIndex); + const scrollToIndex = model.logs.get().findIndex(log => log === firstLog); - setTimeout(() => { - rowVirtualizer.scrollToIndex(scrollToIndex, { align: 'start', smoothScroll: false }); - }, 1000) - - // rowVirtualizer.scrollToIndex(scrollToIndex, { align: 'start', smoothScroll: false }); + rowVirtualizer.scrollToIndex(scrollToIndex, { align: 'start', smoothScroll: false }); } }; From c4538e928b3409bd08f7798583998b04f3ea2138 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Fri, 2 Sep 2022 14:33:00 +0300 Subject: [PATCH 11/73] Scroll to bottom on pod id change Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 03a34da5a4..c1c4e281bc 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -78,7 +78,7 @@ export const LogList = observer(({ model }: LogListProps) => { // Initial scroll to bottom rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); }, 200) - }, []) + }, [model.logTabData.get()?.selectedPodId]) return (
Date: Mon, 5 Sep 2022 13:15:13 +0300 Subject: [PATCH 12/73] Scroll to bottom on tab change Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index c1c4e281bc..cd4830b55a 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -80,6 +80,10 @@ export const LogList = observer(({ model }: LogListProps) => { }, 200) }, [model.logTabData.get()?.selectedPodId]) + useEffect(() => { + rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); + }, [model.logTabData.get()]); + return (
Date: Mon, 5 Sep 2022 13:21:23 +0300 Subject: [PATCH 13/73] Add LogList external styles Signed-off-by: Alex Andreev --- .../components/dock/logs/log-list.module.scss | 24 +++++++++++++++++++ .../components/dock/logs/log-list.tsx | 23 +++++------------- 2 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 src/renderer/components/dock/logs/log-list.module.scss diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss new file mode 100644 index 0000000000..ce0bc802e1 --- /dev/null +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -0,0 +1,24 @@ +.LogList { + flex-grow: 1; + overflow: auto; +} + +.virtualizer { + width: 100%; + position: relative; +} + +.rowWrapper { + position: absolute; + top: 0; + left: 0; + width: 100%; +} + +.lastLine { + width: 100%; + height: 1px; + bottom: 0; + background-color: red; + position: absolute; +} \ No newline at end of file diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index cd4830b55a..f6c89b5804 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -1,3 +1,5 @@ +import styles from "./log-list.module.scss"; + import { useVirtualizer } from '@tanstack/react-virtual'; import AnsiUp from 'ansi_up'; import DOMPurify from 'dompurify'; @@ -87,43 +89,30 @@ export const LogList = observer(({ model }: LogListProps) => { return (
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
))} -
+
) From da8570d97454b584cf7c0d6588cd68e58c1c9f55 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 5 Sep 2022 13:27:32 +0300 Subject: [PATCH 14/73] Add more styles Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.module.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss index ce0bc802e1..545dd0221c 100644 --- a/src/renderer/components/dock/logs/log-list.module.scss +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -1,6 +1,8 @@ .LogList { flex-grow: 1; overflow: auto; + color: var(--logsForeground); + background: var(--logsBackground); } .virtualizer { @@ -13,6 +15,14 @@ top: 0; left: 0; width: 100%; + font-family: var(--font-monospace); + font-size: smaller; + padding: 0 16px; + line-height: 120%; + + > * { + -webkit-font-smoothing: auto; // Better readability on non-retina screens + } } .lastLine { From f09974fd1f60988fa2271b9b8f5b0345523eb236 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 5 Sep 2022 13:36:49 +0300 Subject: [PATCH 15/73] Move LogRow to its own file Signed-off-by: Alex Andreev --- .../components/dock/logs/log-list.module.scss | 2 +- .../components/dock/logs/log-list.tsx | 53 +---------------- .../components/dock/logs/log-row.module.scss | 20 +++++++ src/renderer/components/dock/logs/log-row.tsx | 57 +++++++++++++++++++ 4 files changed, 79 insertions(+), 53 deletions(-) create mode 100644 src/renderer/components/dock/logs/log-row.module.scss create mode 100644 src/renderer/components/dock/logs/log-row.tsx diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss index 545dd0221c..4b005f99d0 100644 --- a/src/renderer/components/dock/logs/log-list.module.scss +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -17,11 +17,11 @@ width: 100%; font-family: var(--font-monospace); font-size: smaller; - padding: 0 16px; line-height: 120%; > * { -webkit-font-smoothing: auto; // Better readability on non-retina screens + padding: 2px 16px; } } diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index f6c89b5804..2bcdf239b3 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -1,13 +1,10 @@ import styles from "./log-list.module.scss"; import { useVirtualizer } from '@tanstack/react-virtual'; -import AnsiUp from 'ansi_up'; -import DOMPurify from 'dompurify'; import { observer } from 'mobx-react'; import React, { useEffect, useRef } from 'react'; -import { SearchStore } from '../../../search-store/search-store'; -import { cssNames } from '../../../utils'; import type { LogTabViewModel } from './logs-view-model'; +import { LogRow } from "./log-row"; export interface LogListProps { model: LogTabViewModel; @@ -118,51 +115,3 @@ export const LogList = observer(({ model }: LogListProps) => { ) }); -const colorConverter = new AnsiUp(); - -function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabViewModel }) { - const { searchQuery, isActiveOverlay } = model.searchStore; - const log = model.visibleLogs.get()[rowIndex]; - const contents: React.ReactElement[] = []; - const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); - - if (searchQuery) { // If search is enabled, replace keyword with backgrounded - // Case-insensitive search (lowercasing query and keywords in line) - const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi"); - const matches = log.matchAll(regex); - const modified = log.replace(regex, match => match.toLowerCase()); - // Splitting text line by keyword - const pieces = modified.split(searchQuery.toLowerCase()); - - pieces.forEach((piece, index) => { - const active = isActiveOverlay(rowIndex, index); - const lastItem = index === pieces.length - 1; - const overlayValue = matches.next().value; - const overlay = !lastItem - ? ( - - ) - : null; - - contents.push( - - - {overlay} - , - ); - }); - } - - return ( -
- {contents.length > 1 ? contents : ( - - )} - {/* For preserving copy-paste experience and keeping line breaks */} -
-
- ); -} \ No newline at end of file diff --git a/src/renderer/components/dock/logs/log-row.module.scss b/src/renderer/components/dock/logs/log-row.module.scss new file mode 100644 index 0000000000..a1f64a7ab1 --- /dev/null +++ b/src/renderer/components/dock/logs/log-row.module.scss @@ -0,0 +1,20 @@ +.overlay { + --overlay-bg: #8cc474b8; + --overlay-active-bg: orange; + + border-radius: 2px; + -webkit-font-smoothing: auto; + background-color: var(--overlay-bg); + + span { + background-color: var(--overlay-bg)!important; // Rewriting inline styles from AnsiUp library + } + + &.active { + background-color: var(--overlay-active-bg); + + span { + background-color: var(--overlay-active-bg)!important; // Rewriting inline styles from AnsiUp library + } + } +} diff --git a/src/renderer/components/dock/logs/log-row.tsx b/src/renderer/components/dock/logs/log-row.tsx new file mode 100644 index 0000000000..bdba64ba48 --- /dev/null +++ b/src/renderer/components/dock/logs/log-row.tsx @@ -0,0 +1,57 @@ +import styles from "./log-row.module.scss"; + +import AnsiUp from 'ansi_up'; +import DOMPurify from 'dompurify'; +import React from 'react'; +import { SearchStore } from '../../../search-store/search-store'; +import { cssNames } from '../../../utils'; +import type { LogTabViewModel } from './logs-view-model'; + +const colorConverter = new AnsiUp(); + +export function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabViewModel }) { + const { searchQuery, isActiveOverlay } = model.searchStore; + const log = model.visibleLogs.get()[rowIndex]; + const contents: React.ReactElement[] = []; + const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); + + if (searchQuery) { // If search is enabled, replace keyword with backgrounded + // Case-insensitive search (lowercasing query and keywords in line) + const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi"); + const matches = log.matchAll(regex); + const modified = log.replace(regex, match => match.toLowerCase()); + // Splitting text line by keyword + const pieces = modified.split(searchQuery.toLowerCase()); + + pieces.forEach((piece, index) => { + const active = isActiveOverlay(rowIndex, index); + const lastItem = index === pieces.length - 1; + const overlayValue = matches.next().value; + const overlay = !lastItem + ? ( + + ) + : null; + + contents.push( + + + {overlay} + , + ); + }); + } + + return ( +
+ {contents.length > 1 ? contents : ( + + )} + {/* For preserving copy-paste experience and keeping line breaks */} +
+
+ ); +} \ No newline at end of file From f90ede1026f0f799f8c0a21b6b0fe8207bd2ffff Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 5 Sep 2022 13:54:57 +0300 Subject: [PATCH 16/73] Scroll to overlay from the search Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 2bcdf239b3..0ea55e56f4 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -83,6 +83,10 @@ export const LogList = observer(({ model }: LogListProps) => { rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); }, [model.logTabData.get()]); + useEffect(() => { + rowVirtualizer.scrollToIndex(model.searchStore.occurrences[model.searchStore.activeOverlayIndex], { align: 'end', smoothScroll: false }); + }, [model.searchStore.activeOverlayIndex]) + return (
Date: Mon, 5 Sep 2022 15:26:24 +0300 Subject: [PATCH 17/73] Add wrapper switch Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/controls.tsx | 12 +++++++++++- .../components/dock/logs/log-list.module.scss | 5 +++++ src/renderer/components/dock/logs/log-list.tsx | 3 ++- src/renderer/components/dock/logs/tab-store.ts | 5 +++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/dock/logs/controls.tsx b/src/renderer/components/dock/logs/controls.tsx index 6738ca56cf..6c3da627cb 100644 --- a/src/renderer/components/dock/logs/controls.tsx +++ b/src/renderer/components/dock/logs/controls.tsx @@ -25,13 +25,17 @@ export const LogControls = observer(({ model }: LogControlsProps) => { } const logs = model.timestampSplitLogs.get(); - const { showTimestamps, showPrevious: previous } = tabData; + const { showTimestamps, showPrevious: previous, wrap } = tabData; const since = logs.length ? logs[0][0] : null; const toggleTimestamps = () => { model.updateLogTabData({ showTimestamps: !showTimestamps }); }; + const toggleWrap = () => { + model.updateLogTabData({ wrap: !wrap }); + }; + const togglePrevious = () => { model.updateLogTabData({ showPrevious: !previous }); model.reloadLogs(); @@ -49,6 +53,12 @@ export const LogControls = observer(({ model }: LogControlsProps) => { )}
+ * { -webkit-font-smoothing: auto; // Better readability on non-retina screens padding: 2px 16px; } + + &.wrap { + white-space: normal; + } } .lastLine { diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 0ea55e56f4..d9bff7e3dd 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -5,6 +5,7 @@ import { observer } from 'mobx-react'; import React, { useEffect, useRef } from 'react'; import type { LogTabViewModel } from './logs-view-model'; import { LogRow } from "./log-row"; +import { cssNames } from "../../../utils"; export interface LogListProps { model: LogTabViewModel; @@ -106,7 +107,7 @@ export const LogList = observer(({ model }: LogListProps) => { style={{ transform: `translateY(${virtualRow.start}px)`, }} - className={styles.rowWrapper} + className={cssNames(styles.rowWrapper, { [styles.wrap]: model.logTabData.get()?.wrap })} >
diff --git a/src/renderer/components/dock/logs/tab-store.ts b/src/renderer/components/dock/logs/tab-store.ts index 9cc83b9610..c50322880e 100644 --- a/src/renderer/components/dock/logs/tab-store.ts +++ b/src/renderer/components/dock/logs/tab-store.ts @@ -54,6 +54,11 @@ export interface LogTabData { * Whether to show the logs of the previous container instance */ showPrevious: boolean; + + /** + * Whether to wrap logs lines to avoid horizontal scrolling. + */ + wrap: boolean; } interface Dependencies { From 5443f40259679d386d309697ddd5f6586efea052 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 16:44:52 +0300 Subject: [PATCH 18/73] Highlight log row on hover Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-row.module.scss | 6 ++++++ src/renderer/components/dock/logs/log-row.tsx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/dock/logs/log-row.module.scss b/src/renderer/components/dock/logs/log-row.module.scss index a1f64a7ab1..948c43716e 100644 --- a/src/renderer/components/dock/logs/log-row.module.scss +++ b/src/renderer/components/dock/logs/log-row.module.scss @@ -1,3 +1,9 @@ +.LogRow { + &:hover { + background: var(--logRowHoverBackground); + } +} + .overlay { --overlay-bg: #8cc474b8; --overlay-active-bg: orange; diff --git a/src/renderer/components/dock/logs/log-row.tsx b/src/renderer/components/dock/logs/log-row.tsx index bdba64ba48..521daa4c2f 100644 --- a/src/renderer/components/dock/logs/log-row.tsx +++ b/src/renderer/components/dock/logs/log-row.tsx @@ -46,7 +46,7 @@ export function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabVie } return ( -
+
{contents.length > 1 ? contents : ( )} From b989340f84ca26db3fd398a73b047af7622716bb Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 16:56:06 +0300 Subject: [PATCH 19/73] Refresh rows measurement by refreshing their keys Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index d9bff7e3dd..4c80ebb9f7 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -6,6 +6,7 @@ import React, { useEffect, useRef } from 'react'; import type { LogTabViewModel } from './logs-view-model'; import { LogRow } from "./log-row"; import { cssNames } from "../../../utils"; +import { v4 as getRandomId } from "uuid"; export interface LogListProps { model: LogTabViewModel; @@ -14,6 +15,7 @@ export interface LogListProps { export const LogList = observer(({ model }: LogListProps) => { const [toBottomVisible, setToBottomVisible] = React.useState(false); const [lastLineVisible, setLastLineVisible] = React.useState(true); + const [rowKeySuffix, setRowKeySuffix] = React.useState(getRandomId()); const { visibleLogs } = model; const parentRef = useRef(null) @@ -82,6 +84,7 @@ export const LogList = observer(({ model }: LogListProps) => { useEffect(() => { rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); + setRowKeySuffix(getRandomId()); }, [model.logTabData.get()]); useEffect(() => { @@ -102,7 +105,7 @@ export const LogList = observer(({ model }: LogListProps) => { > {rowVirtualizer.getVirtualItems().map((virtualRow) => (
Date: Tue, 6 Sep 2022 17:40:05 +0300 Subject: [PATCH 20/73] Create useScrollToBottomButton hook Signed-off-by: Alex Andreev --- .../components/dock/logs/log-list.tsx | 18 ++++-------------- .../dock/logs/use-scroll-to-bottom.ts | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 src/renderer/components/dock/logs/use-scroll-to-bottom.ts diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 4c80ebb9f7..011aa593fa 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -7,18 +7,19 @@ import type { LogTabViewModel } from './logs-view-model'; import { LogRow } from "./log-row"; import { cssNames } from "../../../utils"; import { v4 as getRandomId } from "uuid"; +import { useScrollToBottomButton } from "./use-scroll-to-bottom"; export interface LogListProps { model: LogTabViewModel; } export const LogList = observer(({ model }: LogListProps) => { - const [toBottomVisible, setToBottomVisible] = React.useState(false); const [lastLineVisible, setLastLineVisible] = React.useState(true); const [rowKeySuffix, setRowKeySuffix] = React.useState(getRandomId()); - + const { visibleLogs } = model; const parentRef = useRef(null) + const [toBottomVisible, setButtonVisibility] = useScrollToBottomButton(parentRef.current); const rowVirtualizer = useVirtualizer({ count: visibleLogs.get().length, getScrollElement: () => parentRef.current, @@ -31,22 +32,11 @@ export const LogList = observer(({ model }: LogListProps) => { const onScroll = (event: React.UIEvent) => { if (!parentRef.current) return; - setToBottomVisibility(); + setButtonVisibility(); setLastLineVisibility(); onScrollToTop(); } - // TODO: Move to its own hook - const setToBottomVisibility = () => { - const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; - // console.log("scrolling", scrollHeight, scrollTop, rowVirtualizer.getTotalSize()) - if (scrollHeight - scrollTop > 4000) { - setToBottomVisible(true); - } else { - setToBottomVisible(false); - } - } - const setLastLineVisibility = () => { const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; diff --git a/src/renderer/components/dock/logs/use-scroll-to-bottom.ts b/src/renderer/components/dock/logs/use-scroll-to-bottom.ts new file mode 100644 index 0000000000..76a16be000 --- /dev/null +++ b/src/renderer/components/dock/logs/use-scroll-to-bottom.ts @@ -0,0 +1,19 @@ +import { useState } from 'react'; + +export function useScrollToBottomButton(scrolledParent: HTMLDivElement | null): [isVisible: boolean, setVisibility: () => void] { + const [isVisible, setToBottomVisible] = useState(false); + + const setVisibility = () => { + if (!scrolledParent) return; + + const { scrollTop, scrollHeight } = scrolledParent; + + if (scrollHeight - scrollTop > 4000) { + setToBottomVisible(true); + } else { + setToBottomVisible(false); + } + } + + return [isVisible, setVisibility]; +} \ No newline at end of file From 4c74d5a780c4ae7a78ce6925979a84f4ad96c762 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 18:26:16 +0300 Subject: [PATCH 21/73] Add useInitialScrollToBottom() hook Signed-off-by: Alex Andreev --- .../components/dock/logs/log-list.tsx | 24 ++++++++++--------- .../dock/logs/use-initial-scroll-to-bottom.ts | 10 ++++++++ 2 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 011aa593fa..986d71dc94 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -8,6 +8,7 @@ import { LogRow } from "./log-row"; import { cssNames } from "../../../utils"; import { v4 as getRandomId } from "uuid"; import { useScrollToBottomButton } from "./use-scroll-to-bottom"; +import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom"; export interface LogListProps { model: LogTabViewModel; @@ -25,10 +26,16 @@ export const LogList = observer(({ model }: LogListProps) => { getScrollElement: () => parentRef.current, estimateSize: () => 38, overscan: 5, - scrollPaddingEnd: 0, - scrollPaddingStart: 0, }); + const scrollTo = (index: number) => { + rowVirtualizer.scrollToIndex(index, { align: 'start', smoothScroll: false }); + } + + const scrollToBottom = () => { + scrollTo(visibleLogs.get().length - 1); + } + const onScroll = (event: React.UIEvent) => { if (!parentRef.current) return; @@ -48,7 +55,7 @@ export const LogList = observer(({ model }: LogListProps) => { } /** - * Check if user scrolled to top and new logs should be loaded + * Loads new logs if user scrolled to the top */ const onScrollToTop = async () => { const { scrollTop } = parentRef.current as HTMLDivElement; @@ -61,19 +68,14 @@ export const LogList = observer(({ model }: LogListProps) => { const scrollToIndex = model.logs.get().findIndex(log => log === firstLog); - rowVirtualizer.scrollToIndex(scrollToIndex, { align: 'start', smoothScroll: false }); + scrollTo(scrollToIndex); } }; - useEffect(() => { - setTimeout(() => { - // Initial scroll to bottom - rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); - }, 200) - }, [model.logTabData.get()?.selectedPodId]) + useInitialScrollToBottom(model, scrollToBottom); useEffect(() => { - rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); + // rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); setRowKeySuffix(getRandomId()); }, [model.logTabData.get()]); diff --git a/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts new file mode 100644 index 0000000000..4593e37a3e --- /dev/null +++ b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts @@ -0,0 +1,10 @@ +import { useEffect } from "react"; +import type { LogTabViewModel } from "./logs-view-model"; + +export function useInitialScrollToBottom(model: LogTabViewModel, callback: () => void) { + useEffect(() => { + setTimeout(() => { + callback(); + }, 300) // Giving some time virtual library to render its rows + }, [model.logTabData.get()?.selectedPodId]) +} \ No newline at end of file From b8f4fa14bfbd86cb420f0fa6fb0d4f177144a9d1 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 18:35:02 +0300 Subject: [PATCH 22/73] Show Jump to bottom button Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 986d71dc94..ba498de7ba 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -9,6 +9,7 @@ import { cssNames } from "../../../utils"; import { v4 as getRandomId } from "uuid"; import { useScrollToBottomButton } from "./use-scroll-to-bottom"; import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom"; +import { ToBottom } from "./to-bottom"; export interface LogListProps { model: LogTabViewModel; @@ -111,6 +112,9 @@ export const LogList = observer(({ model }: LogListProps) => { ))}
+ {toBottomVisible && ( + + )}
) }); From 6ec761868bf97f746f1bf29dc848b1d9004823ff Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 18:49:50 +0300 Subject: [PATCH 23/73] Restyling Jump to bottom button Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/to-bottom.module.scss | 8 ++++++++ src/renderer/components/dock/logs/to-bottom.tsx | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 src/renderer/components/dock/logs/to-bottom.module.scss diff --git a/src/renderer/components/dock/logs/to-bottom.module.scss b/src/renderer/components/dock/logs/to-bottom.module.scss new file mode 100644 index 0000000000..e4aaae65cd --- /dev/null +++ b/src/renderer/components/dock/logs/to-bottom.module.scss @@ -0,0 +1,8 @@ +.ToBottom { + position: absolute; + top: 64px; + right: 16px; + border-radius: 4px; + padding: 4px 6px; + background-color: var(--colorInfo); +} \ No newline at end of file diff --git a/src/renderer/components/dock/logs/to-bottom.tsx b/src/renderer/components/dock/logs/to-bottom.tsx index f9b5cbb03e..d7ca86b1bd 100644 --- a/src/renderer/components/dock/logs/to-bottom.tsx +++ b/src/renderer/components/dock/logs/to-bottom.tsx @@ -2,20 +2,20 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import styles from "./to-bottom.module.scss"; + import React from "react"; import { Icon } from "../../icon"; export function ToBottom({ onClick }: { onClick: () => void }) { return ( ); From 51c059f1babd62aa6dbac51cd415d78f8b7a9832 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 18:54:26 +0300 Subject: [PATCH 24/73] Give more room to the LogRows content Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.module.scss | 1 - src/renderer/components/dock/logs/log-row.module.scss | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss index 1d6b4635ed..32ebc636a3 100644 --- a/src/renderer/components/dock/logs/log-list.module.scss +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -22,7 +22,6 @@ > * { -webkit-font-smoothing: auto; // Better readability on non-retina screens - padding: 2px 16px; } &.wrap { diff --git a/src/renderer/components/dock/logs/log-row.module.scss b/src/renderer/components/dock/logs/log-row.module.scss index 948c43716e..be01df5f6d 100644 --- a/src/renderer/components/dock/logs/log-row.module.scss +++ b/src/renderer/components/dock/logs/log-row.module.scss @@ -1,4 +1,7 @@ .LogRow { + padding: 2px calc(var(--padding) * 2); + line-height: 150%; + &:hover { background: var(--logRowHoverBackground); } From 7b53650628467e1187bbe6670490a5ddc329ee80 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 19:05:10 +0300 Subject: [PATCH 25/73] Hook function renaming Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 4 ++-- src/renderer/components/dock/logs/use-scroll-to-bottom.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index ba498de7ba..b95d904956 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -7,7 +7,7 @@ import type { LogTabViewModel } from './logs-view-model'; import { LogRow } from "./log-row"; import { cssNames } from "../../../utils"; import { v4 as getRandomId } from "uuid"; -import { useScrollToBottomButton } from "./use-scroll-to-bottom"; +import { useJumpToBottomButton } from "./use-scroll-to-bottom"; import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom"; import { ToBottom } from "./to-bottom"; @@ -21,7 +21,7 @@ export const LogList = observer(({ model }: LogListProps) => { const { visibleLogs } = model; const parentRef = useRef(null) - const [toBottomVisible, setButtonVisibility] = useScrollToBottomButton(parentRef.current); + const [toBottomVisible, setButtonVisibility] = useJumpToBottomButton(parentRef.current); const rowVirtualizer = useVirtualizer({ count: visibleLogs.get().length, getScrollElement: () => parentRef.current, diff --git a/src/renderer/components/dock/logs/use-scroll-to-bottom.ts b/src/renderer/components/dock/logs/use-scroll-to-bottom.ts index 76a16be000..ef97b909f9 100644 --- a/src/renderer/components/dock/logs/use-scroll-to-bottom.ts +++ b/src/renderer/components/dock/logs/use-scroll-to-bottom.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; -export function useScrollToBottomButton(scrolledParent: HTMLDivElement | null): [isVisible: boolean, setVisibility: () => void] { +export function useJumpToBottomButton(scrolledParent: HTMLDivElement | null): [isVisible: boolean, setVisibility: () => void] { const [isVisible, setToBottomVisible] = useState(false); const setVisibility = () => { From 05a3ed0e57833397a0f12d203462beb69e686e8e Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Tue, 6 Sep 2022 19:28:01 +0300 Subject: [PATCH 26/73] Fix scrolling to items on search Signed-off-by: Alex Andreev --- src/renderer/components/dock/logs/log-list.tsx | 6 ++++-- src/renderer/search-store/search-store.ts | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index b95d904956..e469bea435 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -81,8 +81,10 @@ export const LogList = observer(({ model }: LogListProps) => { }, [model.logTabData.get()]); useEffect(() => { - rowVirtualizer.scrollToIndex(model.searchStore.occurrences[model.searchStore.activeOverlayIndex], { align: 'end', smoothScroll: false }); - }, [model.searchStore.activeOverlayIndex]) + if (!model.searchStore.occurrences.length) return; + + scrollTo(model.searchStore.occurrences[model.searchStore.activeOverlayIndex]); + }, [model.searchStore.searchQuery, model.searchStore.activeOverlayIndex]) return (
Date: Wed, 7 Sep 2022 15:10:32 +0300 Subject: [PATCH 27/73] Add useIntersectionObserver() hook Signed-off-by: alexfront --- src/renderer/hooks/useIntersectionObserver.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/renderer/hooks/useIntersectionObserver.ts diff --git a/src/renderer/hooks/useIntersectionObserver.ts b/src/renderer/hooks/useIntersectionObserver.ts new file mode 100644 index 0000000000..fff4aa3662 --- /dev/null +++ b/src/renderer/hooks/useIntersectionObserver.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { useEffect, useState } from "react"; + +/** + * Intersection Observer configuratiopn options. + */ +interface IntersectionObserverOptions { + /** + * If `true`, check for intersection only once. Will + * disconnect the IntersectionObserver instance after + * intersection. + */ + triggerOnce: boolean; + + /** + * Number from 0 to 1 representing the percentage + * of the element that needs to be visible to be + * considered as visible. Can also be an array of + * thresholds. + */ + threshold: number | number[]; + + /** + * Element that is used as the viewport for checking visibility + * of the provided `ref` or `element`. + */ + root?: Element; + + /** + * Margin around the root. Can have values similar to + * the CSS margin property. + */ + rootMargin?: string; +} + +function useIntersectionObserver( + element: Element, + { + threshold = 0, + rootMargin = "0%", + root, + }: IntersectionObserverOptions, +): IntersectionObserverEntry | undefined { + const [entry, setEntry] = useState(); + + const updateEntry = ([entry]: IntersectionObserverEntry[]): void => { + setEntry(entry); + }; + + useEffect(() => { + if (!element) return; + + const observer = new IntersectionObserver(updateEntry, { threshold, root, rootMargin }); + + observer.observe(element); + + return () => observer.disconnect(); + + }, [element, threshold, root, rootMargin]); + + return entry; +} + +export default useIntersectionObserver; From 1898a4d68e874ff058f7179043357a38d17a1a61 Mon Sep 17 00:00:00 2001 From: alexfront Date: Wed, 7 Sep 2022 15:21:23 +0300 Subject: [PATCH 28/73] Remove unused code Signed-off-by: alexfront --- src/renderer/components/dock/logs/search.tsx | 6 +--- src/renderer/components/dock/logs/view.tsx | 32 ++------------------ 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/src/renderer/components/dock/logs/search.tsx b/src/renderer/components/dock/logs/search.tsx index 892874c53a..286a626e8d 100644 --- a/src/renderer/components/dock/logs/search.tsx +++ b/src/renderer/components/dock/logs/search.tsx @@ -13,11 +13,10 @@ import type { LogTabViewModel } from "./logs-view-model"; export interface PodLogSearchProps { onSearch?: (query: string) => void; - scrollToOverlay: (lineNumber: number | undefined) => void; model: LogTabViewModel; } -export const LogSearch = observer(({ onSearch, scrollToOverlay, model: { logTabData, searchStore, ...model }}: PodLogSearchProps) => { +export const LogSearch = observer(({ onSearch, model: { logTabData, searchStore, ...model }}: PodLogSearchProps) => { const tabData = logTabData.get(); if (!tabData) { @@ -33,17 +32,14 @@ export const LogSearch = observer(({ onSearch, scrollToOverlay, model: { logTabD const setSearch = (query: string) => { searchStore.onSearch(logs, query); onSearch?.(query); - scrollToOverlay(searchStore.activeOverlayLine); }; const onPrevOverlay = () => { setPrevOverlayActive(); - scrollToOverlay(searchStore.activeOverlayLine); }; const onNextOverlay = () => { setNextOverlayActive(); - scrollToOverlay(searchStore.activeOverlayLine); }; const onClear = () => { diff --git a/src/renderer/components/dock/logs/view.tsx b/src/renderer/components/dock/logs/view.tsx index 19d1b84691..63154653ba 100644 --- a/src/renderer/components/dock/logs/view.tsx +++ b/src/renderer/components/dock/logs/view.tsx @@ -3,12 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import React, { createRef, useEffect } from "react"; +import React, { useEffect } from "react"; import { observer } from "mobx-react"; import { InfoPanel } from "../info-panel"; import { LogResourceSelector } from "./resource-selector"; -import type { LogListRef } from "./list"; -// import { LogList } from "./list"; import { LogSearch } from "./search"; import { LogControls } from "./controls"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -20,7 +18,6 @@ import type { SubscribeStores } from "../../../kube-watch-api/kube-watch-api"; import subscribeStoresInjectable from "../../../kube-watch-api/subscribe-stores.injectable"; import type { PodStore } from "../../+workloads-pods/store"; import podStoreInjectable from "../../+workloads-pods/store.injectable"; -import { noop } from "lodash"; import { LogList } from "./log-list"; export interface LogsDockTabProps { @@ -41,7 +38,6 @@ const NonInjectedLogsDockTab = observer(({ subscribeStores, podStore, }: Dependencies & LogsDockTabProps) => { - const logListElement = createRef(); const data = model.logTabData.get(); useEffect(() => { @@ -55,27 +51,6 @@ const NonInjectedLogsDockTab = observer(({ namespaces: data ? [data.namespace] : [], }), [data?.namespace]); - const scrollToOverlay = (overlayLine: number | undefined) => { - if (!logListElement.current || overlayLine === undefined) { - return; - } - - // Scroll vertically - logListElement.current.scrollToItem(overlayLine, "center"); - // Scroll horizontally in timeout since virtual list need some time to prepare its contents - setTimeout(() => { - const overlay = document.querySelector(".PodLogs .list span.active"); - - if (!overlay) return; - // Note: .scrollIntoViewIfNeeded() is non-standard and thus not present in js-dom. - overlay?.scrollIntoViewIfNeeded?.(); - }, 100); - }; - - if (!data) { - return null; - } - return (
- +
)} showSubmitClose={false} From d37ae1bffe608c3c64cef28996bf33b0b339d65f Mon Sep 17 00:00:00 2001 From: alexfront Date: Wed, 7 Sep 2022 15:24:47 +0300 Subject: [PATCH 29/73] Fixing ts compilation errors Signed-off-by: alexfront --- .../pod-logs/download-logs.test.tsx | 1 + .../__test__/log-resource-selector.test.tsx | 3 +++ .../dock/logs/__test__/log-search.test.tsx | 22 +++++----------- .../dock/logs/create-logs-tab.injectable.ts | 1 + .../components/dock/logs/log-list.tsx | 26 ++++++++++++------- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/behaviours/pod-logs/download-logs.test.tsx b/src/behaviours/pod-logs/download-logs.test.tsx index 824428ee89..8c69a746f1 100644 --- a/src/behaviours/pod-logs/download-logs.test.tsx +++ b/src/behaviours/pod-logs/download-logs.test.tsx @@ -56,6 +56,7 @@ describe("download logs options in pod logs dock tab", () => { namespace: "default", showPrevious: true, showTimestamps: false, + wrap: false, })); windowDi.override(setLogTabDataInjectable, () => jest.fn()); windowDi.override(loadLogsInjectable, () => jest.fn()); diff --git a/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx b/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx index 66874bfd9f..20e779a55e 100644 --- a/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx +++ b/src/renderer/components/dock/logs/__test__/log-resource-selector.test.tsx @@ -46,6 +46,7 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial { if (id === selectedPod.getId()) { @@ -101,6 +103,7 @@ const getFewPodsTabData = (tabId: TabId, deps: Partial { if (id === selectedPod.getId()) { diff --git a/src/renderer/components/dock/logs/__test__/log-search.test.tsx b/src/renderer/components/dock/logs/__test__/log-search.test.tsx index bf41bb8252..cf503772aa 100644 --- a/src/renderer/components/dock/logs/__test__/log-search.test.tsx +++ b/src/renderer/components/dock/logs/__test__/log-search.test.tsx @@ -20,6 +20,7 @@ function mockLogTabViewModel(tabId: TabId, deps: Partial { if (id === selectedPod.getId()) { @@ -71,10 +73,7 @@ describe("LogSearch tests", () => { it("renders w/o errors", () => { const model = getOnePodViewModel("foobar"); const { container } = render( - , + , ); expect(container).toBeInstanceOf(HTMLElement); @@ -90,10 +89,7 @@ describe("LogSearch tests", () => { }); render( - , + , ); userEvent.click(await screen.findByPlaceholderText("Search...")); @@ -112,10 +108,7 @@ describe("LogSearch tests", () => { }); render( - , + , ); userEvent.click(await screen.findByPlaceholderText("Search...")); @@ -134,10 +127,7 @@ describe("LogSearch tests", () => { }); render( - , + , ); userEvent.click(await screen.findByText("keyboard_arrow_down")); diff --git a/src/renderer/components/dock/logs/create-logs-tab.injectable.ts b/src/renderer/components/dock/logs/create-logs-tab.injectable.ts index 44e3e8a0ec..38a3bf109b 100644 --- a/src/renderer/components/dock/logs/create-logs-tab.injectable.ts +++ b/src/renderer/components/dock/logs/create-logs-tab.injectable.ts @@ -31,6 +31,7 @@ const createLogsTab = ({ createDockTab, setLogTabData, getRandomId }: Dependenci setLogTabData(id, { showTimestamps: false, showPrevious: false, + wrap: false, ...data, }); }); diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index e469bea435..c966c76211 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -1,5 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + import styles from "./log-list.module.scss"; +import throttle from "lodash/throttle"; import { useVirtualizer } from '@tanstack/react-virtual'; import { observer } from 'mobx-react'; import React, { useEffect, useRef } from 'react'; @@ -16,7 +22,7 @@ export interface LogListProps { } export const LogList = observer(({ model }: LogListProps) => { - const [lastLineVisible, setLastLineVisible] = React.useState(true); + // const [lastLineVisible, setLastLineVisible] = React.useState(true); const [rowKeySuffix, setRowKeySuffix] = React.useState(getRandomId()); const { visibleLogs } = model; @@ -37,28 +43,28 @@ export const LogList = observer(({ model }: LogListProps) => { scrollTo(visibleLogs.get().length - 1); } - const onScroll = (event: React.UIEvent) => { + const onScroll = throttle(() => { if (!parentRef.current) return; setButtonVisibility(); setLastLineVisibility(); onScrollToTop(); - } + }, 1_000, { trailing: true, leading: true }); const setLastLineVisibility = () => { - const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; + // const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; - if (scrollHeight - scrollTop < 4000) { - setLastLineVisible(true); - } else { - setLastLineVisible(false); - } + // if (scrollHeight - scrollTop < 4000) { + // setLastLineVisible(true); + // } else { + // setLastLineVisible(false); + // } } /** * Loads new logs if user scrolled to the top */ - const onScrollToTop = async () => { + const onScrollToTop = async () => { const { scrollTop } = parentRef.current as HTMLDivElement; if (scrollTop === 0) { From ac8ed1478ebd2f6ff3cb1733b47ef339a359be8a Mon Sep 17 00:00:00 2001 From: alexfront Date: Thu, 8 Sep 2022 14:14:35 +0300 Subject: [PATCH 30/73] Scroll list to bottom when new logs arrived Signed-off-by: alexfront --- .../components/dock/logs/log-list.module.scss | 2 +- .../components/dock/logs/log-list.tsx | 30 +++++++++---------- src/renderer/hooks/useIntersectionObserver.ts | 6 ++-- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss index 32ebc636a3..459262ba41 100644 --- a/src/renderer/components/dock/logs/log-list.module.scss +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -33,6 +33,6 @@ width: 100%; height: 1px; bottom: 0; - background-color: red; + background-color: var(--logsBackground); position: absolute; } \ No newline at end of file diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index c966c76211..3bf75b7371 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -16,18 +16,20 @@ import { v4 as getRandomId } from "uuid"; import { useJumpToBottomButton } from "./use-scroll-to-bottom"; import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom"; import { ToBottom } from "./to-bottom"; +import useIntersectionObserver from "../../../hooks/useIntersectionObserver"; export interface LogListProps { model: LogTabViewModel; } export const LogList = observer(({ model }: LogListProps) => { - // const [lastLineVisible, setLastLineVisible] = React.useState(true); - const [rowKeySuffix, setRowKeySuffix] = React.useState(getRandomId()); - const { visibleLogs } = model; - const parentRef = useRef(null) + const parentRef = useRef(null); + const lastLineRef = useRef(null); + const [rowKeySuffix, setRowKeySuffix] = React.useState(getRandomId()); const [toBottomVisible, setButtonVisibility] = useJumpToBottomButton(parentRef.current); + const entry = useIntersectionObserver(lastLineRef.current, {}); + const rowVirtualizer = useVirtualizer({ count: visibleLogs.get().length, getScrollElement: () => parentRef.current, @@ -47,20 +49,9 @@ export const LogList = observer(({ model }: LogListProps) => { if (!parentRef.current) return; setButtonVisibility(); - setLastLineVisibility(); onScrollToTop(); }, 1_000, { trailing: true, leading: true }); - const setLastLineVisibility = () => { - // const { scrollTop, scrollHeight } = parentRef.current as HTMLDivElement; - - // if (scrollHeight - scrollTop < 4000) { - // setLastLineVisible(true); - // } else { - // setLastLineVisible(false); - // } - } - /** * Loads new logs if user scrolled to the top */ @@ -83,6 +74,7 @@ export const LogList = observer(({ model }: LogListProps) => { useEffect(() => { // rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); + // Refresh list setRowKeySuffix(getRandomId()); }, [model.logTabData.get()]); @@ -92,6 +84,12 @@ export const LogList = observer(({ model }: LogListProps) => { scrollTo(model.searchStore.occurrences[model.searchStore.activeOverlayIndex]); }, [model.searchStore.searchQuery, model.searchStore.activeOverlayIndex]) + useEffect(() => { + if (entry?.isIntersecting) { + scrollToBottom(); + } + }, [model.visibleLogs.get().length]); + return (
{
))} -
+
{toBottomVisible && ( diff --git a/src/renderer/hooks/useIntersectionObserver.ts b/src/renderer/hooks/useIntersectionObserver.ts index fff4aa3662..03faa47948 100644 --- a/src/renderer/hooks/useIntersectionObserver.ts +++ b/src/renderer/hooks/useIntersectionObserver.ts @@ -14,7 +14,7 @@ interface IntersectionObserverOptions { * disconnect the IntersectionObserver instance after * intersection. */ - triggerOnce: boolean; + triggerOnce?: boolean; /** * Number from 0 to 1 representing the percentage @@ -22,7 +22,7 @@ interface IntersectionObserverOptions { * considered as visible. Can also be an array of * thresholds. */ - threshold: number | number[]; + threshold?: number | number[]; /** * Element that is used as the viewport for checking visibility @@ -38,7 +38,7 @@ interface IntersectionObserverOptions { } function useIntersectionObserver( - element: Element, + element: Element | null, { threshold = 0, rootMargin = "0%", From d951f3a0174b1a780afae74f3ab50dfee4d4aa2a Mon Sep 17 00:00:00 2001 From: alexfront Date: Thu, 8 Sep 2022 16:00:01 +0300 Subject: [PATCH 31/73] Move all side effects to hooks Signed-off-by: alexfront --- .../components/dock/logs/log-list.module.scss | 8 +++ .../components/dock/logs/log-list.tsx | 66 ++++++------------- .../components/dock/logs/use-on-scroll-top.ts | 37 +++++++++++ .../logs/use-refresh-list-on-data-change.ts | 19 ++++++ .../dock/logs/use-scroll-on-search.ts | 17 +++++ .../logs/use-stick-to-bottom-on-logs-load.ts | 25 +++++++ 6 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 src/renderer/components/dock/logs/use-on-scroll-top.ts create mode 100644 src/renderer/components/dock/logs/use-refresh-list-on-data-change.ts create mode 100644 src/renderer/components/dock/logs/use-scroll-on-search.ts create mode 100644 src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss index 459262ba41..a72072f40a 100644 --- a/src/renderer/components/dock/logs/log-list.module.scss +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -29,6 +29,14 @@ } } +.firstLine { + width: 100%; + height: 1px; + top: 0; + background-color: var(--logsBackground); + position: absolute; +} + .lastLine { width: 100%; height: 1px; diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index 3bf75b7371..d170da5c07 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -5,18 +5,19 @@ import styles from "./log-list.module.scss"; -import throttle from "lodash/throttle"; import { useVirtualizer } from '@tanstack/react-virtual'; import { observer } from 'mobx-react'; -import React, { useEffect, useRef } from 'react'; -import type { LogTabViewModel } from './logs-view-model'; -import { LogRow } from "./log-row"; +import React, { useRef } from 'react'; import { cssNames } from "../../../utils"; -import { v4 as getRandomId } from "uuid"; -import { useJumpToBottomButton } from "./use-scroll-to-bottom"; -import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom"; +import { LogRow } from "./log-row"; +import type { LogTabViewModel } from './logs-view-model'; 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 { model: LogTabViewModel; @@ -25,10 +26,9 @@ export interface LogListProps { export const LogList = observer(({ model }: LogListProps) => { const { visibleLogs } = model; const parentRef = useRef(null); - const lastLineRef = useRef(null); - const [rowKeySuffix, setRowKeySuffix] = React.useState(getRandomId()); + const topLineRef = useRef(null); + const bottomLineRef = useRef(null); const [toBottomVisible, setButtonVisibility] = useJumpToBottomButton(parentRef.current); - const entry = useIntersectionObserver(lastLineRef.current, {}); const rowVirtualizer = useVirtualizer({ count: visibleLogs.get().length, @@ -45,50 +45,21 @@ export const LogList = observer(({ model }: LogListProps) => { scrollTo(visibleLogs.get().length - 1); } - const onScroll = throttle(() => { + const onScroll = () => { if (!parentRef.current) return; 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); - useEffect(() => { - // rowVirtualizer.scrollToIndex(visibleLogs.get().length - 1, { align: 'end', smoothScroll: false }); - // Refresh list - setRowKeySuffix(getRandomId()); - }, [model.logTabData.get()]); + const uniqRowKey = useRefreshListOnDataChange(model.logTabData.get()); - useEffect(() => { - if (!model.searchStore.occurrences.length) return; + useScrollOnSearch(model.searchStore, scrollTo); - scrollTo(model.searchStore.occurrences[model.searchStore.activeOverlayIndex]); - }, [model.searchStore.searchQuery, model.searchStore.activeOverlayIndex]) + useStickToBottomOnLogsLoad({ bottomLineRef, model, scrollToBottom }); - useEffect(() => { - if (entry?.isIntersecting) { - scrollToBottom(); - } - }, [model.visibleLogs.get().length]); + useOnScrollTop({ topLineRef, model, scrollTo }); return (
{ }} className={styles.virtualizer} > +
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
{
))} -
+
{toBottomVisible && ( diff --git a/src/renderer/components/dock/logs/use-on-scroll-top.ts b/src/renderer/components/dock/logs/use-on-scroll-top.ts new file mode 100644 index 0000000000..1452b0791a --- /dev/null +++ b/src/renderer/components/dock/logs/use-on-scroll-top.ts @@ -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; + 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]); +} diff --git a/src/renderer/components/dock/logs/use-refresh-list-on-data-change.ts b/src/renderer/components/dock/logs/use-refresh-list-on-data-change.ts new file mode 100644 index 0000000000..983b83a323 --- /dev/null +++ b/src/renderer/components/dock/logs/use-refresh-list-on-data-change.ts @@ -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; +} diff --git a/src/renderer/components/dock/logs/use-scroll-on-search.ts b/src/renderer/components/dock/logs/use-scroll-on-search.ts new file mode 100644 index 0000000000..8d6d0ad9ff --- /dev/null +++ b/src/renderer/components/dock/logs/use-scroll-on-search.ts @@ -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]); +} diff --git a/src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts b/src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts new file mode 100644 index 0000000000..3f2628f064 --- /dev/null +++ b/src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts @@ -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; + 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]); +} From 2d69bbcfd847e139dd14ac66a4c4597a5b2c59a8 Mon Sep 17 00:00:00 2001 From: alexfront Date: Thu, 8 Sep 2022 16:04:57 +0300 Subject: [PATCH 32/73] Fix linter errors Signed-off-by: alexfront --- .../dock/logs/get-visible-logs.injectable.ts | 12 ++++++++---- src/renderer/components/dock/logs/log-list.tsx | 16 ++++++++-------- src/renderer/components/dock/logs/log-row.tsx | 18 +++++++++++------- .../dock/logs/use-initial-scroll-to-bottom.ts | 10 +++++++--- .../dock/logs/use-scroll-to-bottom.ts | 10 +++++++--- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/renderer/components/dock/logs/get-visible-logs.injectable.ts b/src/renderer/components/dock/logs/get-visible-logs.injectable.ts index debd13f84a..d2df3f87d6 100644 --- a/src/renderer/components/dock/logs/get-visible-logs.injectable.ts +++ b/src/renderer/components/dock/logs/get-visible-logs.injectable.ts @@ -1,3 +1,7 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ import { getInjectable } from "@ogre-tools/injectable"; import moment from "moment"; import userStoreInjectable from "../../../../common/user-store/user-store.injectable"; @@ -13,7 +17,7 @@ const getVisibleLogsInjectable = getInjectable({ return (tabId: TabId) => { const getLogTabData = di.inject(getLogTabDataInjectable); const getTimestampSplitLogs = di.inject(getTimestampSplitLogsInjectable); - const userStore = di.inject(userStoreInjectable) + const userStore = di.inject(userStoreInjectable); const logTabData = getLogTabData(tabId); if (!logTabData) { @@ -31,8 +35,8 @@ const getVisibleLogsInjectable = getInjectable({ return getTimestampSplitLogs(tabId).map(([logTimestamp, log]) => ( `${logTimestamp && moment.tz(logTimestamp, userStore.localeTimezone).format()}${log}` )); - } - } + }; + }, }); -export default getVisibleLogsInjectable; \ No newline at end of file +export default getVisibleLogsInjectable; diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index d170da5c07..edcf1f7030 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -5,12 +5,12 @@ import styles from "./log-list.module.scss"; -import { useVirtualizer } from '@tanstack/react-virtual'; -import { observer } from 'mobx-react'; -import React, { useRef } from 'react'; +import { useVirtualizer } from "@tanstack/react-virtual"; +import { observer } from "mobx-react"; +import React, { useRef } from "react"; import { cssNames } from "../../../utils"; import { LogRow } from "./log-row"; -import type { LogTabViewModel } from './logs-view-model'; +import type { LogTabViewModel } from "./logs-view-model"; import { ToBottom } from "./to-bottom"; import { useInitialScrollToBottom } from "./use-initial-scroll-to-bottom"; import { useOnScrollTop } from "./use-on-scroll-top"; @@ -38,12 +38,12 @@ export const LogList = observer(({ model }: LogListProps) => { }); const scrollTo = (index: number) => { - rowVirtualizer.scrollToIndex(index, { align: 'start', smoothScroll: false }); - } + rowVirtualizer.scrollToIndex(index, { align: "start", smoothScroll: false }); + }; const scrollToBottom = () => { scrollTo(visibleLogs.get().length - 1); - } + }; const onScroll = () => { if (!parentRef.current) return; @@ -94,6 +94,6 @@ export const LogList = observer(({ model }: LogListProps) => { )}
- ) + ); }); diff --git a/src/renderer/components/dock/logs/log-row.tsx b/src/renderer/components/dock/logs/log-row.tsx index 521daa4c2f..b3fd56c56d 100644 --- a/src/renderer/components/dock/logs/log-row.tsx +++ b/src/renderer/components/dock/logs/log-row.tsx @@ -1,11 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ import styles from "./log-row.module.scss"; -import AnsiUp from 'ansi_up'; -import DOMPurify from 'dompurify'; -import React from 'react'; -import { SearchStore } from '../../../search-store/search-store'; -import { cssNames } from '../../../utils'; -import type { LogTabViewModel } from './logs-view-model'; +import AnsiUp from "ansi_up"; +import DOMPurify from "dompurify"; +import React from "react"; +import { SearchStore } from "../../../search-store/search-store"; +import { cssNames } from "../../../utils"; +import type { LogTabViewModel } from "./logs-view-model"; const colorConverter = new AnsiUp(); @@ -54,4 +58,4 @@ export function LogRow({ rowIndex, model }: { rowIndex: number; model: LogTabVie
); -} \ No newline at end of file +} diff --git a/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts index 4593e37a3e..164df8aab6 100644 --- a/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts +++ b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts @@ -1,3 +1,7 @@ +/** + * 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 { LogTabViewModel } from "./logs-view-model"; @@ -5,6 +9,6 @@ export function useInitialScrollToBottom(model: LogTabViewModel, callback: () => useEffect(() => { setTimeout(() => { callback(); - }, 300) // Giving some time virtual library to render its rows - }, [model.logTabData.get()?.selectedPodId]) -} \ No newline at end of file + }, 300); // Giving some time virtual library to render its rows + }, [model.logTabData.get()?.selectedPodId]); +} diff --git a/src/renderer/components/dock/logs/use-scroll-to-bottom.ts b/src/renderer/components/dock/logs/use-scroll-to-bottom.ts index ef97b909f9..8a8cb72cc9 100644 --- a/src/renderer/components/dock/logs/use-scroll-to-bottom.ts +++ b/src/renderer/components/dock/logs/use-scroll-to-bottom.ts @@ -1,4 +1,8 @@ -import { useState } from 'react'; +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { useState } from "react"; export function useJumpToBottomButton(scrolledParent: HTMLDivElement | null): [isVisible: boolean, setVisibility: () => void] { const [isVisible, setToBottomVisible] = useState(false); @@ -13,7 +17,7 @@ export function useJumpToBottomButton(scrolledParent: HTMLDivElement | null): [i } else { setToBottomVisible(false); } - } + }; return [isVisible, setVisibility]; -} \ No newline at end of file +} From 64773f3b69082401dc586906f2eca001643ed39c Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 09:58:59 +0300 Subject: [PATCH 33/73] Unify styles for the top and bottom lines Signed-off-by: alexfront --- .../components/dock/logs/log-list.module.scss | 11 +---------- src/renderer/components/dock/logs/log-list.tsx | 12 ++++++++++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.module.scss b/src/renderer/components/dock/logs/log-list.module.scss index a72072f40a..27e2389884 100644 --- a/src/renderer/components/dock/logs/log-list.module.scss +++ b/src/renderer/components/dock/logs/log-list.module.scss @@ -29,18 +29,9 @@ } } -.firstLine { +.anchorLine { width: 100%; height: 1px; - top: 0; background-color: var(--logsBackground); position: absolute; } - -.lastLine { - width: 100%; - height: 1px; - bottom: 0; - background-color: var(--logsBackground); - position: absolute; -} \ No newline at end of file diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index edcf1f7030..e46a3c2fb8 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -73,7 +73,11 @@ export const LogList = observer(({ model }: LogListProps) => { }} className={styles.virtualizer} > -
+
{rowVirtualizer.getVirtualItems().map((virtualRow) => (
{
))} -
+
{toBottomVisible && ( From 511fc0941918e1970df0114ea252e21f22d5c38f Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 09:59:42 +0300 Subject: [PATCH 34/73] Prevent useEffect not firing with because of condition Signed-off-by: alexfront --- src/renderer/components/dock/logs/search.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/renderer/components/dock/logs/search.tsx b/src/renderer/components/dock/logs/search.tsx index 286a626e8d..a2200e1a4b 100644 --- a/src/renderer/components/dock/logs/search.tsx +++ b/src/renderer/components/dock/logs/search.tsx @@ -19,11 +19,7 @@ export interface PodLogSearchProps { export const LogSearch = observer(({ onSearch, model: { logTabData, searchStore, ...model }}: PodLogSearchProps) => { const tabData = logTabData.get(); - if (!tabData) { - return null; - } - - const logs = tabData.showTimestamps + const logs = tabData?.showTimestamps ? model.logs.get() : model.logsWithoutTimestamps.get(); const { setNextOverlayActive, setPrevOverlayActive, searchQuery, occurrences, activeFind, totalFinds } = searchStore; From e82f83fa5a7022d5e38c58c2b64982f5cba2fc21 Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 10:00:42 +0300 Subject: [PATCH 35/73] Use named export for useIntersectionObserver Signed-off-by: alexfront --- src/renderer/components/dock/logs/use-on-scroll-top.ts | 2 +- .../components/dock/logs/use-stick-to-bottom-on-logs-load.ts | 2 +- src/renderer/hooks/index.ts | 1 + src/renderer/hooks/useIntersectionObserver.ts | 4 +--- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/dock/logs/use-on-scroll-top.ts b/src/renderer/components/dock/logs/use-on-scroll-top.ts index 1452b0791a..95e20ca378 100644 --- a/src/renderer/components/dock/logs/use-on-scroll-top.ts +++ b/src/renderer/components/dock/logs/use-on-scroll-top.ts @@ -5,7 +5,7 @@ import type { RefObject } from "react"; import { useEffect } from "react"; -import useIntersectionObserver from "../../../hooks/useIntersectionObserver"; +import { useIntersectionObserver } from "../../../hooks"; import type { LogTabViewModel } from "./logs-view-model"; interface UseStickToBottomProps { diff --git a/src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts b/src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts index 3f2628f064..e59290ac68 100644 --- a/src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts +++ b/src/renderer/components/dock/logs/use-stick-to-bottom-on-logs-load.ts @@ -5,7 +5,7 @@ import type { RefObject } from "react"; import { useEffect } from "react"; -import useIntersectionObserver from "../../../hooks/useIntersectionObserver"; +import { useIntersectionObserver } from "../../../hooks"; import type { LogTabViewModel } from "./logs-view-model"; interface UseStickToBottomProps { diff --git a/src/renderer/hooks/index.ts b/src/renderer/hooks/index.ts index 852f4d64d0..d6b06e416b 100644 --- a/src/renderer/hooks/index.ts +++ b/src/renderer/hooks/index.ts @@ -10,3 +10,4 @@ export * from "./useInterval"; export * from "./useMutationObserver"; export * from "./useResizeObserver"; export * from "./use-toggle"; +export * from "./useIntersectionObserver"; diff --git a/src/renderer/hooks/useIntersectionObserver.ts b/src/renderer/hooks/useIntersectionObserver.ts index 03faa47948..41d51ac8a4 100644 --- a/src/renderer/hooks/useIntersectionObserver.ts +++ b/src/renderer/hooks/useIntersectionObserver.ts @@ -37,7 +37,7 @@ interface IntersectionObserverOptions { rootMargin?: string; } -function useIntersectionObserver( +export function useIntersectionObserver( element: Element | null, { threshold = 0, @@ -64,5 +64,3 @@ function useIntersectionObserver( return entry; } - -export default useIntersectionObserver; From aeb0127a8f69a533c34a3d9eda6a2880ee5002b5 Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 11:04:24 +0300 Subject: [PATCH 36/73] Disable smooth scrolling Signed-off-by: alexfront --- src/renderer/components/dock/logs/log-list.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/renderer/components/dock/logs/log-list.tsx b/src/renderer/components/dock/logs/log-list.tsx index e46a3c2fb8..f928d92bb9 100644 --- a/src/renderer/components/dock/logs/log-list.tsx +++ b/src/renderer/components/dock/logs/log-list.tsx @@ -29,12 +29,14 @@ export const LogList = observer(({ model }: LogListProps) => { const topLineRef = useRef(null); const bottomLineRef = useRef(null); const [toBottomVisible, setButtonVisibility] = useJumpToBottomButton(parentRef.current); + const uniqRowKey = useRefreshListOnDataChange(model.logTabData.get()); const rowVirtualizer = useVirtualizer({ count: visibleLogs.get().length, getScrollElement: () => parentRef.current, estimateSize: () => 38, overscan: 5, + enableSmoothScroll: false, }); const scrollTo = (index: number) => { @@ -52,13 +54,8 @@ export const LogList = observer(({ model }: LogListProps) => { }; useInitialScrollToBottom(model, scrollToBottom); - - const uniqRowKey = useRefreshListOnDataChange(model.logTabData.get()); - useScrollOnSearch(model.searchStore, scrollTo); - useStickToBottomOnLogsLoad({ bottomLineRef, model, scrollToBottom }); - useOnScrollTop({ topLineRef, model, scrollTo }); return ( From c4f41db2759bcc800d9f433860ab4996103872d7 Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 11:06:41 +0300 Subject: [PATCH 37/73] Increase timeout delay to wait virtualizer Signed-off-by: alexfront --- .../components/dock/logs/use-initial-scroll-to-bottom.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts index 164df8aab6..98910c77ab 100644 --- a/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts +++ b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts @@ -7,8 +7,9 @@ import type { LogTabViewModel } from "./logs-view-model"; export function useInitialScrollToBottom(model: LogTabViewModel, callback: () => void) { useEffect(() => { + // TODO: Consider more precise way to check when list ready to scroll setTimeout(() => { callback(); - }, 300); // Giving some time virtual library to render its rows + }, 800); // Giving some time virtual library to render its rows }, [model.logTabData.get()?.selectedPodId]); } From 0383666f230004bbf55603e7e49b901188560afb Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 11:26:42 +0300 Subject: [PATCH 38/73] Remove unused files Signed-off-by: alexfront --- src/renderer/components/dock/logs/list.scss | 71 ----- src/renderer/components/dock/logs/list.tsx | 278 -------------------- 2 files changed, 349 deletions(-) delete mode 100644 src/renderer/components/dock/logs/list.scss delete mode 100644 src/renderer/components/dock/logs/list.tsx diff --git a/src/renderer/components/dock/logs/list.scss b/src/renderer/components/dock/logs/list.scss deleted file mode 100644 index 58b49f4ab7..0000000000 --- a/src/renderer/components/dock/logs/list.scss +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -.LogList { - --overlay-bg: #8cc474b8; - --overlay-active-bg: orange; - - // fix for `this.logsElement.scrollTop = this.logsElement.scrollHeight` - // `overflow: overlay` don't allow scroll to the last line - overflow: auto; - - position: relative; - color: var(--logsForeground); - background: var(--logsBackground); - flex-grow: 1; - - .VirtualList { - height: 100%; - - .list { - overflow-x: scroll!important; - - .LogRow { - padding: 2px 16px; - height: 18px; // Must be equal to lineHeight variable in pod-log-list.tsx - font-family: var(--font-monospace); - font-size: smaller; - white-space: nowrap; - - &:hover { - background: var(--logRowHoverBackground); - } - - span { - -webkit-font-smoothing: auto; // Better readability on non-retina screens - white-space: pre; - } - - span.overlay { - border-radius: 2px; - -webkit-font-smoothing: auto; - background-color: var(--overlay-bg); - - span { - background-color: var(--overlay-bg)!important; // Rewriting inline styles from AnsiUp library - } - - &.active { - background-color: var(--overlay-active-bg); - - span { - background-color: var(--overlay-active-bg)!important; // Rewriting inline styles from AnsiUp library - } - } - } - } - } - } - - &.isLoading { - cursor: wait; - } - - &.isScrollHidden { - .VirtualList .list { - overflow-x: hidden!important; // fixing scroll to bottom issues in PodLogs - } - } -} diff --git a/src/renderer/components/dock/logs/list.tsx b/src/renderer/components/dock/logs/list.tsx deleted file mode 100644 index 2976a258fa..0000000000 --- a/src/renderer/components/dock/logs/list.tsx +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import "./list.scss"; - -import type { ForwardedRef } from "react"; -import React from "react"; -import AnsiUp from "ansi_up"; -import DOMPurify from "dompurify"; -import debounce from "lodash/debounce"; -import { action, computed, observable, makeObservable, reaction } from "mobx"; -import { disposeOnUnmount, observer } from "mobx-react"; -import moment from "moment-timezone"; -import type { Align, ListOnScrollProps } from "react-window"; -import { SearchStore } from "../../../search-store/search-store"; -import { UserStore } from "../../../../common/user-store"; -import { array, autoBind, cssNames } from "../../../utils"; -import type { VirtualListRef } from "../../virtual-list"; -import { VirtualList } from "../../virtual-list"; -import { ToBottom } from "./to-bottom"; -import type { LogTabViewModel } from "../logs/logs-view-model"; -import { Spinner } from "../../spinner"; - -export interface LogListProps { - model: LogTabViewModel; -} - -const colorConverter = new AnsiUp(); - -export interface LogListRef { - scrollToItem: (index: number, align: Align) => void; -} - -@observer -class NonForwardedLogList extends React.Component }> { - @observable isJumpButtonVisible = false; - @observable isLastLineVisible = true; - - private virtualListDiv = React.createRef(); // A reference for outer container in VirtualList - private virtualListRef = React.createRef(); // A reference for VirtualList component - private lineHeight = 18; // Height of a log line. Should correlate with styles in pod-log-list.scss - - constructor(props: any) { - super(props); - makeObservable(this); - autoBind(this); - } - - componentDidMount() { - disposeOnUnmount(this, [ - reaction(() => this.props.model.logs.get(), (logs, prevLogs) => { - this.onLogsInitialLoad(logs, prevLogs); - this.onLogsUpdate(); - this.onUserScrolledUp(logs, prevLogs); - }), - ]); - this.bindInnerRef({ - scrollToItem: this.scrollToItem, - }); - } - - componentDidUpdate() { - this.bindInnerRef({ - scrollToItem: this.scrollToItem, - }); - } - - componentWillUnmount() { - this.bindInnerRef(null); - } - - private bindInnerRef(value: LogListRef | null) { - if (typeof this.props.innerRef === "function") { - this.props.innerRef(value); - } else if (this.props.innerRef && typeof this.props.innerRef === "object") { - this.props.innerRef.current = value; - } - } - - onLogsInitialLoad(logs: string[], prevLogs: string[]) { - if (!prevLogs.length && logs.length) { - this.isLastLineVisible = true; - } - } - - onLogsUpdate() { - if (this.isLastLineVisible) { - setTimeout(() => { - this.scrollToBottom(); - }, 500); // Giving some time to VirtualList to prepare its outerRef (this.virtualListDiv) element - } - } - - onUserScrolledUp(logs: string[], prevLogs: string[]) { - if (!this.virtualListDiv.current) return; - - const newLogsAdded = prevLogs.length < logs.length; - const scrolledToBeginning = this.virtualListDiv.current.scrollTop === 0; - - if (newLogsAdded && scrolledToBeginning) { - const firstLineContents = prevLogs[0]; - const lineToScroll = logs.findIndex((value) => value == firstLineContents); - - if (lineToScroll !== -1) { - this.scrollToItem(lineToScroll, "start"); - } - } - } - - /** - * Returns logs with or without timestamps regarding to showTimestamps prop - */ - @computed - get logs(): string[] { - const { showTimestamps } = this.props.model.logTabData.get() ?? {}; - - if (!showTimestamps) { - return this.props.model.logsWithoutTimestamps.get(); - } - - return this.props.model.timestampSplitLogs - .get() - .map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, UserStore.getInstance().localeTimezone).format()}${log}`)); - } - - /** - * Checks if JumpToBottom button should be visible and sets its observable - * @param props Scrolling props from virtual list core - */ - setButtonVisibility = action(({ scrollOffset }: ListOnScrollProps, { scrollHeight }: HTMLDivElement) => { - const offset = 100 * this.lineHeight; - - if (scrollHeight - scrollOffset < offset) { - this.isJumpButtonVisible = false; - } else { - this.isJumpButtonVisible = true; - } - }); - - /** - * Checks if last log line considered visible to user, setting its observable - * @param props Scrolling props from virtual list core - */ - setLastLineVisibility = action(({ scrollOffset }: ListOnScrollProps, { scrollHeight, clientHeight }: HTMLDivElement) => { - this.isLastLineVisible = (clientHeight + scrollOffset) === scrollHeight; - }); - - /** - * Check if user scrolled to top and new logs should be loaded - * @param props Scrolling props from virtual list core - */ - checkLoadIntent = (props: ListOnScrollProps) => { - const { scrollOffset } = props; - - if (scrollOffset === 0) { - this.props.model.loadLogs(); - } - }; - - scrollToBottom = () => { - if (!this.virtualListDiv.current) return; - this.virtualListDiv.current.scrollTop = this.virtualListDiv.current.scrollHeight; - }; - - scrollToItem = (index: number, align: Align) => { - this.virtualListRef.current?.scrollToItem(index, align); - }; - - onScroll = (props: ListOnScrollProps) => { - this.isLastLineVisible = false; - this.onScrollDebounced(props); - }; - - onScrollDebounced = debounce((props: ListOnScrollProps) => { - const virtualList = this.virtualListDiv.current; - - if (virtualList) { - this.setButtonVisibility(props, virtualList); - this.setLastLineVisibility(props, virtualList); - this.checkLoadIntent(props); - } - }, 700); // Increasing performance and giving some time for virtual list to settle down - - /** - * A function is called by VirtualList for rendering each of the row - * @param rowIndex index of the log element in logs array - * @returns A react element with a row itself - */ - getLogRow = (rowIndex: number) => { - const { searchQuery, isActiveOverlay } = this.props.model.searchStore; - const item = this.logs[rowIndex]; - const contents: React.ReactElement[] = []; - const ansiToHtml = (ansi: string) => DOMPurify.sanitize(colorConverter.ansi_to_html(ansi)); - - if (searchQuery) { // If search is enabled, replace keyword with backgrounded - // Case-insensitive search (lowercasing query and keywords in line) - const regex = new RegExp(SearchStore.escapeRegex(searchQuery), "gi"); - const matches = item.matchAll(regex); - const modified = item.replace(regex, match => match.toLowerCase()); - // Splitting text line by keyword - const pieces = modified.split(searchQuery.toLowerCase()); - - pieces.forEach((piece, index) => { - const active = isActiveOverlay(rowIndex, index); - const lastItem = index === pieces.length - 1; - const overlayValue = matches.next().value; - const overlay = !lastItem - ? ( - - ) - : null; - - contents.push( - - - {overlay} - , - ); - }); - } - - return ( -
- {contents.length > 1 ? contents : ( - - )} - {/* For preserving copy-paste experience and keeping line breaks */} -
-
- ); - }; - - render() { - if (this.props.model.isLoading.get()) { - return ( -
- -
- ); - } - - if (!this.logs.length) { - return ( -
- There are no logs available for container - {" "} - {this.props.model.logTabData.get()?.selectedContainer} -
- ); - } - - return ( -
- - {this.isJumpButtonVisible && ( - - )} -
- ); - } -} - -export const LogList = React.forwardRef((props, ref) => ( - -)); From 0de9b9c168d31d92d8790b6b17e5ef8bcbf9917a Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 11:27:44 +0300 Subject: [PATCH 39/73] Decrease scroll to bottom timeout time a bit Signed-off-by: alexfront --- .../components/dock/logs/use-initial-scroll-to-bottom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts index 98910c77ab..c412d50425 100644 --- a/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts +++ b/src/renderer/components/dock/logs/use-initial-scroll-to-bottom.ts @@ -10,6 +10,6 @@ export function useInitialScrollToBottom(model: LogTabViewModel, callback: () => // TODO: Consider more precise way to check when list ready to scroll setTimeout(() => { callback(); - }, 800); // Giving some time virtual library to render its rows + }, 500); // Giving some time virtual library to render its rows }, [model.logTabData.get()?.selectedPodId]); } From 85426ace802835dfe6634e95246fa6e7935da428 Mon Sep 17 00:00:00 2001 From: alexfront Date: Fri, 9 Sep 2022 11:47:35 +0300 Subject: [PATCH 40/73] Remove a leftover Signed-off-by: alexfront --- src/renderer/components/dock/logs/view.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/components/dock/logs/view.tsx b/src/renderer/components/dock/logs/view.tsx index 63154653ba..4c42b08ff0 100644 --- a/src/renderer/components/dock/logs/view.tsx +++ b/src/renderer/components/dock/logs/view.tsx @@ -65,7 +65,6 @@ const NonInjectedLogsDockTab = observer(({ showButtons={false} showStatusPanel={false} /> - {/* */}
From 7ce064c21b89394b197e04e34191724ea82ef601 Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 28 Nov 2022 10:17:20 +0300 Subject: [PATCH 41/73] Fixing to-bottom tests Signed-off-by: Alex Andreev --- .../components/dock/logs/__test__/to-bottom.test.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx b/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx index 9e4177c1dd..f529169c2a 100644 --- a/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx +++ b/src/renderer/components/dock/logs/__test__/to-bottom.test.tsx @@ -26,12 +26,6 @@ describe("", () => { expect(container).toBeInstanceOf(HTMLElement); }); - it("has 'To bottom' label", () => { - const { getByText } = render(); - - expect(getByText("To bottom")).toBeInTheDocument(); - }); - it("has a arrow down icon", () => { const { getByText } = render(); @@ -42,7 +36,7 @@ describe("", () => { const callback = jest.fn(); const { getByText } = render(); - fireEvent.click(getByText("To bottom")); + fireEvent.click(getByText("expand_more")); expect(callback).toBeCalled(); }); }); From 6b2b9bcc0f44e4f85cee57195483f706535bfa1d Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 28 Nov 2022 10:18:56 +0300 Subject: [PATCH 42/73] Bump @tanstack/react-virtual version Signed-off-by: Alex Andreev --- package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ff1aed0141..c4e50a67be 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "@sentry/electron": "^3.0.8", "@sentry/integrations": "^6.19.3", "@side/jest-runtime": "^1.0.1", - "@tanstack/react-virtual": "3.0.0-beta.18", + "@tanstack/react-virtual": "3.0.0-beta.23", "@types/circular-dependency-plugin": "5.0.5", "abort-controller": "^3.0.0", "auto-bind": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index a92d814814..e36d38937b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1712,17 +1712,17 @@ dependencies: defer-to-connect "^2.0.0" -"@tanstack/react-virtual@3.0.0-beta.18": - version "3.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.18.tgz#b97b2019f7d6a5770fb88ee1f7591da55b9059b4" - integrity sha512-mnyCZT6htcRNw1jVb+WyfMUMbd1UmXX/JWPuMf6Bmj92DB/V7Ogk5n5rby5Y5aste7c7mlsBeMF8HtpwERRvEQ== +"@tanstack/react-virtual@3.0.0-beta.23": + version "3.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.23.tgz#f3d3e049d6b49e2b91c46ec3d35f48d9fdf7426a" + integrity sha512-FurqZJD7oSXLtL5NP0YQKXNEY+2qczXjzxmxkD51ZO8ykyr2z62IRNfvpOyly2CPLg+M346mA/Ul2hlx4R3KPw== dependencies: - "@tanstack/virtual-core" "3.0.0-beta.18" + "@tanstack/virtual-core" "3.0.0-beta.23" -"@tanstack/virtual-core@3.0.0-beta.18": - version "3.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.18.tgz#d4b0738c1d0aada922063c899675ff4df9f696b2" - integrity sha512-tcXutY05NpN9lp3+AXI9Sn85RxSPV0EJC0XMim9oeQj/E7bjXoL0qZ4Er4wwnvIbv/hZjC91EmbIQGjgdr6nZg== +"@tanstack/virtual-core@3.0.0-beta.23": + version "3.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.23.tgz#2e586256bdb239e35c8d1ca4e8d66c1c55151419" + integrity sha512-xQfJgz4mdaxifPjOqUBYAar60UAQTrED2SpS0VI5AZvxYI4NDTxx5cFrjaP4baKY3UnVc8IsGFbqr8Q/LZNMag== "@testing-library/dom@>=7", "@testing-library/dom@^8.0.0": version "8.13.0" From d598ee5c3787ffe051eeb79f773fe772bbe4364d Mon Sep 17 00:00:00 2001 From: Alex Andreev Date: Mon, 28 Nov 2022 10:46:55 +0300 Subject: [PATCH 43/73] Fix download-logs tests Signed-off-by: Alex Andreev --- .../__snapshots__/download-logs.test.tsx.snap | 86 ++++++++++++++----- src/features/pod-logs/download-logs.test.tsx | 11 +++ 2 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/features/pod-logs/__snapshots__/download-logs.test.tsx.snap b/src/features/pod-logs/__snapshots__/download-logs.test.tsx.snap index 442d92dbe9..58bcc2277b 100644 --- a/src/features/pod-logs/__snapshots__/download-logs.test.tsx.snap +++ b/src/features/pod-logs/__snapshots__/download-logs.test.tsx.snap @@ -746,31 +746,35 @@ exports[`download logs options in logs dock tab opening pod logs when logs avail
-
-
+
+
+
-
- - some-logs - -
-
+ + some-logs + +
+
+
- There are no logs available for container - - docker-exporter +
+
+
+
+