diff --git a/ui/package.json b/ui/package.json index 6068aea91f914..29e4fee8ad3fd 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,6 +14,7 @@ "@fortawesome/fontawesome-free": "^6.5.2", "@types/react-virtualized": "^9.21.21", "@types/superagent": "^8.1.6", + "@tanstack/react-virtual": "^3.10.7", "ansi-to-react": "^6.1.6", "argo-ui": "git+https://github.com/argoproj/argo-ui.git", "buffer": "^6.0.3", diff --git a/ui/src/app/applications/components/application-resources-diff/application-resources-diff-section.tsx b/ui/src/app/applications/components/application-resources-diff/application-resources-diff-section.tsx new file mode 100644 index 0000000000000..34aa0863362f8 --- /dev/null +++ b/ui/src/app/applications/components/application-resources-diff/application-resources-diff-section.tsx @@ -0,0 +1,141 @@ +import * as React from 'react'; +import {addQueueItem} from './diff-queue'; +import {parseDiff} from 'react-diff-view'; +import {useVirtualizer} from '@tanstack/react-virtual'; +import {IndividualDiffSection} from './individual-diff-section'; + +interface DiffFileModel { + loading: boolean; + file?: any; +} + +export interface ApplicationResourcesDiffSectionProps { + prepareDiff: { + a: string; + b: string; + name: string; + hook?: boolean; + }[]; + compactDiff: boolean; + inlineDiff: boolean; +} + +export const ApplicationResourcesDiffSection = (props: ApplicationResourcesDiffSectionProps) => { + const {prepareDiff, compactDiff, inlineDiff} = props; + const parentRef = React.useRef(); + + const whiteBox = prepareDiff.length > 1 ? 'white-box' : ''; + const showPath = prepareDiff.length > 1; + const viewType = inlineDiff ? 'unified' : 'split'; + + const [diffProgress, setDiffProgress] = React.useState({ + finished: 0, + total: prepareDiff.length + }); + + const [diffFiles, setDiffFiles] = React.useState( + prepareDiff.map(() => ({ + loading: true, + file: {} + })) + ); + + React.useEffect(() => { + prepareDiff.forEach((filePrepare, i) => { + addQueueItem({ + a: filePrepare.a, + b: filePrepare.b, + name: filePrepare.name, + compactDiff: compactDiff, + inlineDiff: inlineDiff + }).then((diffText: string) => { + const files = parseDiff(diffText); + + setDiffFiles(diffLines => { + const newDiffFiles = [...diffLines]; + newDiffFiles[i] = { + loading: false, + file: files[0] + }; + + setDiffProgress(val => { + return { + ...val, + finished: val.finished + 1 + }; + }); + return newDiffFiles; + }); + }); + }); + + return () => { + setDiffProgress({ + finished: 0, + total: prepareDiff.length + }); + setDiffFiles( + prepareDiff.map(() => ({ + loading: true, + file: {} + })) + ); + }; + }, [compactDiff, prepareDiff]); + + // The virtualizer + const rowVirtualizer = useVirtualizer({ + count: prepareDiff.length, + getScrollElement: () => parentRef.current, + estimateSize: () => 35 + }); + + const virtualizeItems = rowVirtualizer.getVirtualItems(); + + return ( + <> + {diffProgress.finished < diffProgress.total && ( +
+ {diffProgress.finished} / {diffProgress.total} files diff +
+ )} +
+
+
+ {virtualizeItems.map((virtualRow: any) => ( + + ))} +
+
+
+ + ); +}; diff --git a/ui/src/app/applications/components/application-resources-diff/application-resources-diff.tsx b/ui/src/app/applications/components/application-resources-diff/application-resources-diff.tsx index 97721a337a0fa..93f092174703f 100644 --- a/ui/src/app/applications/components/application-resources-diff/application-resources-diff.tsx +++ b/ui/src/app/applications/components/application-resources-diff/application-resources-diff.tsx @@ -4,7 +4,8 @@ import * as React from 'react'; import 'react-diff-view/style/index.css'; import * as models from '../../../shared/models'; import {services} from '../../../shared/services'; -import {ApplicationResourcesDiffItem, clearQueue, disableQueue, enableQueue} from './application-resources-diff-item'; +import {clearQueue, disableQueue, enableQueue} from './diff-queue'; +import {ApplicationResourcesDiffSection} from './application-resources-diff-section'; import './application-resources-diff.scss'; @@ -20,26 +21,26 @@ export const ApplicationResourcesDiff = (props: ApplicationResourcesDiffProps) = disableQueue(); }; }, []); + + const diffTextPrepare = props.states + .map(state => { + return { + a: state.normalizedLiveState ? jsYaml.dump(state.normalizedLiveState, {indent: 2}) : '', + b: state.predictedLiveState ? jsYaml.dump(state.predictedLiveState, {indent: 2}) : '', + hook: state.hook, + // doubles as sort order + name: (state.group || '') + '/' + state.kind + '/' + (state.namespace ? state.namespace + '/' : '') + state.name + }; + }) + .filter(i => !i.hook) + .filter(i => i.a !== i.b) + .sort((a, b) => a.name.localeCompare(b.name)); + return ( services.viewPreferences.getPreferences()}> {pref => { - const diffTextPrepare = props.states - .map(state => { - return { - a: state.normalizedLiveState ? jsYaml.dump(state.normalizedLiveState, {indent: 2}) : '', - b: state.predictedLiveState ? jsYaml.dump(state.predictedLiveState, {indent: 2}) : '', - hook: state.hook, - // doubles as sort order - name: (state.group || '') + '/' + state.kind + '/' + (state.namespace ? state.namespace + '/' : '') + state.name - }; - }) - .filter(i => !i.hook) - .filter(i => i.a !== i.b); - // assume that if you only have one file, we don't need the file path const whiteBox = props.states.length > 1 ? 'white-box' : ''; - const showPath = props.states.length > 1; - return (
@@ -74,20 +75,7 @@ export const ApplicationResourcesDiff = (props: ApplicationResourcesDiffProps) = />
- {diffTextPrepare - .sort((a, b) => a.name.localeCompare(b.name)) - .map(item => ( - - ))} +
); }} diff --git a/ui/src/app/applications/components/application-resources-diff/application-resources-diff-item.tsx b/ui/src/app/applications/components/application-resources-diff/diff-queue.ts similarity index 56% rename from ui/src/app/applications/components/application-resources-diff/application-resources-diff-item.tsx rename to ui/src/app/applications/components/application-resources-diff/diff-queue.ts index 404fc58bdba67..e1c62cfd6fa00 100644 --- a/ui/src/app/applications/components/application-resources-diff/application-resources-diff-item.tsx +++ b/ui/src/app/applications/components/application-resources-diff/diff-queue.ts @@ -1,7 +1,4 @@ -import React from 'react'; import {diffLines, formatLines} from 'unidiff'; -import {parseDiff} from 'react-diff-view'; -import {IndividualDiffSection} from './individual-diff-section'; const calcMaxTime = 100; @@ -30,10 +27,6 @@ export interface AsyncDiffModel { reject?: CallableFunction; } -interface DiffFileModel { - [key: string]: any; -} - export const diffQueue: AsyncDiffModel[] = []; let startIdx = 0; @@ -93,52 +86,6 @@ export function processQueue() { } if (enabled) { - requestAnimationFrame(processQueue); + requestIdleCallback(processQueue); } } - -export interface ApplicationResourcesDiffItemProps { - compactDiff: boolean; - inlineDiff: boolean; - a: string; - b: string; - name: string; - whiteBox: string; - showPath: boolean; - hook: boolean; -} - -export const ApplicationResourcesDiffItem = (props: ApplicationResourcesDiffItemProps) => { - const {showPath, whiteBox, inlineDiff} = props; - const viewType = inlineDiff ? 'unified' : 'split'; - const [loading, updateLoading] = React.useState(true); - const [diffFile, setDiffFile] = React.useState({}); - - let isExit = false; - - React.useEffect(() => { - updateLoading(true); - addQueueItem({ - compactDiff: props.compactDiff, - a: props.a, - b: props.b, - name: props.name - }).then((diffText: string) => { - const files = parseDiff(diffText); - if (isExit) { - return; - } - setDiffFile({ - ...files[0] - }); - - updateLoading(false); - }); - - return () => { - isExit = true; - }; - }, [showPath, inlineDiff, props.compactDiff, props.a, props.b, props.name]); - - return ; -}; diff --git a/ui/src/app/applications/components/application-resources-diff/individual-diff-section.tsx b/ui/src/app/applications/components/application-resources-diff/individual-diff-section.tsx index 37e6bde769415..0a4f237eee2e9 100644 --- a/ui/src/app/applications/components/application-resources-diff/individual-diff-section.tsx +++ b/ui/src/app/applications/components/application-resources-diff/individual-diff-section.tsx @@ -11,13 +11,14 @@ export interface IndividualDiffSectionProps { whiteBox: string; viewType: string; loading?: boolean; + dataIndex?: number; } -export const IndividualDiffSection = (props: IndividualDiffSectionProps) => { +export const IndividualDiffSection = React.forwardRef((props: IndividualDiffSectionProps, ref) => { const {file, showPath, whiteBox, viewType, loading} = props; const [collapsed, setCollapsed] = useState(false); return ( -
+
{showPath && (

{file.newPath} @@ -34,4 +35,4 @@ export const IndividualDiffSection = (props: IndividualDiffSectionProps) => { ))}

); -}; +}); diff --git a/ui/yarn.lock b/ui/yarn.lock index 231e740c2f649..5aef577357f7c 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1916,6 +1916,18 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@tanstack/react-virtual@^3.10.7": + version "3.10.7" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.10.7.tgz#428d0c29c6d2046ab905f4278446a3fe89bfd4ef" + integrity sha512-yeP+M0G8D+15ZFPivpuQ5hoM4Fa/PzERBx8P8EGcfEsXX3JOb9G9UUrqc47ZXAxvK+YqzM9T5qlJUYUFOwCZJw== + dependencies: + "@tanstack/virtual-core" "3.10.7" + +"@tanstack/virtual-core@3.10.7": + version "3.10.7" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.10.7.tgz#fb1d8ae1257c9fe3a6e99c911cad0d1a1b07f598" + integrity sha512-ND5dfsU0n9F4gROzwNNDJmg6y8n9pI8YWxtgbfJ5UcNn7Hx+MxEXtXcQ189tS7sh8pmCObgz2qSiyRKTZxT4dg== + "@tippy.js/react@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@tippy.js/react/-/react-3.1.1.tgz#027e4595e55f31430741fe8e0d92aaddfbe47efd"