Skip to content

Commit

Permalink
feat(ui): add virtual scroll in app diff avoid to crash
Browse files Browse the repository at this point in the history
Signed-off-by: linghaoSu <linghao.su@daocloud.io>
  • Loading branch information
linghaoSu committed Sep 13, 2024
1 parent cffa9e7 commit 07eef52
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 87 deletions.
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<DiffFileModel[]>(
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 && (
<div style={{marginBottom: '10px'}}>
{diffProgress.finished} / {diffProgress.total} files diff
</div>
)}
<div
ref={parentRef}
style={{
height: 'calc(100vh - 260px)',
width: '100%',
overflowY: 'auto',
contain: 'strict'
}}>
<div
style={{
height: rowVirtualizer.getTotalSize(),
width: '100%',
position: 'relative'
}}>
<div
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualizeItems[0]?.start ?? 0}px)`
}}>
{virtualizeItems.map((virtualRow: any) => (
<IndividualDiffSection
key={virtualRow.key}
dataIndex={virtualRow.index}
ref={rowVirtualizer.measureElement}
file={diffFiles[virtualRow.index].file}
showPath={showPath}
loading={diffFiles[virtualRow.index].loading}
whiteBox={whiteBox}
viewType={viewType}
/>
))}
</div>
</div>
</div>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 (
<DataLoader key='resource-diff' load={() => 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 (
<div className='application-resources-diff'>
<div className={whiteBox + ' application-resources-diff__checkboxes'}>
Expand Down Expand Up @@ -74,20 +75,7 @@ export const ApplicationResourcesDiff = (props: ApplicationResourcesDiffProps) =
/>
<label htmlFor='inlineDiff'>Inline diff</label>
</div>
{diffTextPrepare
.sort((a, b) => a.name.localeCompare(b.name))
.map(item => (
<ApplicationResourcesDiffItem
key={item.name}
a={item.a}
b={item.b}
name={item.name}
hook={item.hook}
showPath={showPath}
whiteBox={whiteBox}
compactDiff={pref.appDetails.compactDiff}
inlineDiff={pref.appDetails.inlineDiff}></ApplicationResourcesDiffItem>
))}
<ApplicationResourcesDiffSection prepareDiff={diffTextPrepare} compactDiff={pref.appDetails.compactDiff} inlineDiff={pref.appDetails.inlineDiff} />
</div>
);
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -30,10 +27,6 @@ export interface AsyncDiffModel {
reject?: CallableFunction;
}

interface DiffFileModel {
[key: string]: any;
}

export const diffQueue: AsyncDiffModel[] = [];

let startIdx = 0;
Expand Down Expand Up @@ -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<DiffFileModel>({});

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 <IndividualDiffSection file={diffFile} showPath={showPath} loading={loading} whiteBox={whiteBox} viewType={viewType} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement, IndividualDiffSectionProps>((props: IndividualDiffSectionProps, ref) => {
const {file, showPath, whiteBox, viewType, loading} = props;
const [collapsed, setCollapsed] = useState(false);
return (
<div className={`${whiteBox} application-component-diff__diff`}>
<div data-index={props.dataIndex} ref={ref} className={`${whiteBox} application-component-diff__diff`}>
{showPath && (
<p className='application-resources-diff__diff__title'>
{file.newPath}
Expand All @@ -34,4 +35,4 @@ export const IndividualDiffSection = (props: IndividualDiffSectionProps) => {
))}
</div>
);
};
});
12 changes: 12 additions & 0 deletions ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 07eef52

Please sign in to comment.