Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(app): improve rendering code preview on large files #3284

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 83 additions & 92 deletions weave-js/src/common/components/FileBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Tooltip from '@mui/material/Tooltip';
import * as _ from 'lodash';
import numeral from 'numeral';
import Prism from 'prismjs';
import React, {FC, useCallback, useEffect, useRef, useState} from 'react';
import React, {FC, memo, useCallback, useEffect, useRef, useState} from 'react';
import TimeAgo from 'react-timeago';
import {Header, Icon, Pagination, Segment, Table} from 'semantic-ui-react';

Expand Down Expand Up @@ -608,103 +608,94 @@ interface CodePreviewProps {
language?: string;
}

const CodePreview: FC<CodePreviewProps> = ({useLoadFile, file, language}) => {
const [data, setDataVal] = useState('');
const [error, setErrorVal] = useState<string | undefined>(undefined);
const ref = useRef<HTMLDivElement>(null);
const setData = useCallback(
(d: string) => {
// Automatically reformat JSON
let lines = d.split('\n');
if (
(file.name.endsWith('.json') && lines.length === 1) ||
(lines.length === 2 && lines[1] === '')
) {
try {
const parsed = JSON.parse(lines[0]);
lines = JSON.stringify(parsed, undefined, 2).split('\n');
} catch {
// ok
}
}

// Truncate long lines
const truncated = lines
.map(line => {
if (line.length > 1000) {
return line.slice(0, 1000) + ' (line truncated to 1000 characters)';
} else {
return line;
const CodePreview: FC<CodePreviewProps> = memo(
({useLoadFile, file, language}) => {
const [data, setDataVal] = useState('');
const [error, setErrorVal] = useState<string | undefined>(undefined);
const ref = useRef<HTMLDivElement>(null);
const setData = useCallback(
(d: string) => {
// Automatically reformat JSON
let lines = d.split('\n');
if (
(file.name.endsWith('.json') && lines.length === 1) ||
(lines.length === 2 && lines[1] === '')
) {
try {
const parsed = JSON.parse(lines[0]);
lines = JSON.stringify(parsed, undefined, 2).split('\n');
} catch {
// ok
}
})
.join('\n');
}

setDataVal(truncated);
},
[setDataVal, file.name]
);
const setError = useCallback(
(errorString?: string) => setErrorVal(errorString || 'Error loading file'),
[setErrorVal]
);
// Truncate long lines
const truncated = lines
.map(line => {
if (line.length > 1000) {
return (
line.slice(0, 1000) + ' (line truncated to 1000 characters)'
);
} else {
return line;
}
})
.join('\n');

setDataVal(truncated);
},
[setDataVal, file.name]
);
const setError = useCallback(
(errorString?: string) =>
setErrorVal(errorString || 'Error loading file'),
[setErrorVal]
);

// We don't pass a fallback to allow dev mode zero byte files to render
const loading = useLoadFile(file, {
onSuccess: setData,
onFailure: setError,
});
useEffect(() => {
if (ref.current != null) {
Prism.highlightElement(ref.current);
// We don't pass a fallback to allow dev mode zero byte files to render
const loading = useLoadFile(file, {
onSuccess: setData,
onFailure: setError,
});
useEffect(() => {
if (ref.current != null && file.sizeBytes / 1024 < 1024) {
onx2 marked this conversation as resolved.
Show resolved Hide resolved
Prism.highlightElement(ref.current);
}
});
if (error != null) {
return <Segment textAlign="center">{error}</Segment>;
}
});
if (error != null) {
return <Segment textAlign="center">{error}</Segment>;
}
if (loading) {
return <Loader name="code-preview-loader" />;
}
// HACKING TO DISPLAY VOC
// if (file.name.endsWith('.xml')) {
// const parser = new DOMParser();
// const xmlDoc = parser.parseFromString(data, 'text/xml');
// const anno = xmlDoc.getElementsByTagName('annotation')[0];
// if (anno != null) {
// for (let i = 0; i < anno.childNodes.length; i++) {
// const node = anno.childNodes[i];
// if (node.nodeType !== Node.TEXT_NODE && node.nodeName === 'filename') {
// const filename = node.childNodes[0].textContent;
// console.log('FILE NAME', filename);
// // const imageFile = (node as any).getElementsByTagName('filename')[0];
// // console.log('IMAGE FILE', imageFile);
// }
// console.log(node);
// }
// // anno.childNodes[]
// // console.log('VOC!');
// }
// }
return (
<div
style={{
background: 'white',
border: '1px solid #eee',
padding: 16,
}}>
<pre
if (loading) {
return <Loader name="code-preview-loader" />;
}

return (
<div
style={{
maxWidth: '100%',
background: 'white',
border: '1px solid #eee',
padding: 16,
height: 'calc(100vh - 150px)',
onx2 marked this conversation as resolved.
Show resolved Hide resolved
overflowY: 'auto',
}}>
<code
style={{whiteSpace: 'pre-wrap', wordBreak: 'break-all'}}
ref={ref}
className={language != null ? `language-${language}` : undefined}>
{data}
</code>
</pre>
</div>
);
};
<pre
style={{
maxWidth: '100%',
}}>
<code
style={{whiteSpace: 'pre-wrap', wordBreak: 'break-all'}}
ref={ref}
className={language != null ? `language-${language}` : undefined}>
{data}
</code>
</pre>
</div>
);
},
(prevProps, nextProps) => {
onx2 marked this conversation as resolved.
Show resolved Hide resolved
return prevProps.file.url === nextProps.file.url;
}
);

interface MarkdownPreviewProps {
useLoadFile: UseLoadFile;
Expand Down
Loading