Refactoring: Integrating the file preview component (#11523)

### What problem does this PR solve?

Refactoring: Integrating the file preview component

### Type of change

- [x] Refactoring
This commit is contained in:
chanx
2025-11-25 19:13:00 +08:00
committed by GitHub
parent a793dd2ea8
commit 5d0981d046
38 changed files with 216 additions and 1222 deletions

View File

@ -1,282 +0,0 @@
// Copyright (c) 2017 PlanGrid, Inc.
.docxViewerWrapper {
overflow-y: scroll;
height: 100%;
width: 100%;
.box {
width: 100%;
height: 100%;
}
:global(.document-container) {
padding: 30px;
width: 700px;
background: rgba(255, 255, 255, 0.1);
margin: auto;
}
html,
bodyaddress,
blockquote,
body,
dd,
div,
dl,
dt,
fieldset,
form,
frame,
frameset,
h1,
h2,
h3,
h4,
h5,
h6,
noframes,
ol,
p,
ul,
center,
dir,
hr,
menu,
pre {
display: block;
unicode-bidi: embed;
}
li {
display: list-item;
list-style-type: disc;
}
head {
display: none;
}
table {
display: table;
}
img {
width: 100%;
}
tr {
display: table-row;
}
thead {
display: table-header-group;
}
tbody {
display: table-row-group;
}
tfoot {
display: table-footer-group;
}
col {
display: table-column;
}
colgroup {
display: table-column-group;
}
th {
display: table-cell;
}
td {
display: table-cell;
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
padding: 0.2em 0.5em;
}
caption {
display: table-caption;
}
th {
font-weight: bolder;
text-align: center;
}
caption {
text-align: center;
}
body {
margin: 8px;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
h2 {
font-size: 1.5em;
margin: 0.75em 0;
}
h3 {
font-size: 1.17em;
margin: 0.83em 0;
}
h4,
p,
blockquote,
ul,
fieldset,
form,
ol,
dl,
dir,
menu {
margin: 1.12em 0;
}
h5 {
font-size: 0.83em;
margin: 1.5em 0;
}
h6 {
font-size: 0.75em;
margin: 1.67em 0;
}
h1,
h2,
h3,
h4,
h5,
h6,
b,
strong {
font-weight: bolder;
}
blockquote {
margin-left: 40px;
margin-right: 40px;
}
i,
cite,
em,
var,
address {
font-style: italic;
}
pre,
tt,
code,
kbd,
samp {
font-family: monospace;
}
pre {
white-space: pre;
}
button,
textarea,
input,
select {
display: inline-block;
}
big {
font-size: 1.17em;
}
small,
sub,
sup {
font-size: 0.83em;
}
sub {
vertical-align: sub;
}
sup {
vertical-align: super;
}
table {
border-spacing: 2px;
}
thead,
tbody,
tfoot {
vertical-align: middle;
}
td,
th,
tr {
vertical-align: inherit;
}
s,
strike,
del {
text-decoration: line-through;
}
hr {
border: 1px inset;
}
ol,
ul,
dir,
menu,
dd {
margin-left: 40px;
}
ol {
list-style-type: decimal;
}
ol ul,
ol ul,
ul ol,
ul ol,
ul ul,
ul ul,
ol ol,
ol ol {
margin-top: 0;
margin-bottom: 0;
}
u,
ins {
text-decoration: underline;
}
br:before {
content: '\A';
white-space: pre-line;
}
center {
text-align: center;
}
:link,
:visited {
text-decoration: underline;
}
:focus {
outline: thin dotted invert;
}
/* Begin bidirectionality settings (do not change) */
BDO[DIR='ltr'] {
direction: ltr;
unicode-bidi: bidi-override;
}
BDO[DIR='rtl'] {
direction: rtl;
unicode-bidi: bidi-override;
}
*[DIR='ltr'] {
direction: ltr;
unicode-bidi: embed;
}
*[DIR='rtl'] {
direction: rtl;
unicode-bidi: embed;
}
@media print {
h1 {
page-break-before: always;
}
h1,
h2,
h3,
h4,
h5,
h6 {
page-break-after: avoid;
}
ul,
ol,
dl {
page-break-before: avoid;
}
}
}

View File

@ -1,25 +0,0 @@
import { Spin } from 'antd';
import FileError from '../file-error';
import { useFetchDocx } from '../hooks';
import styles from './index.less';
const Docx = ({ filePath }: { filePath: string }) => {
const { succeed, containerRef, error } = useFetchDocx(filePath);
return (
<>
{succeed ? (
<section className={styles.docxViewerWrapper}>
<div id="docx" ref={containerRef} className={styles.box}>
<Spin />
</div>
</section>
) : (
<FileError>{error}</FileError>
)}
</>
);
};
export default Docx;

View File

@ -1,19 +0,0 @@
import '@js-preview/excel/lib/index.css';
import FileError from '../file-error';
import { useFetchExcel } from '../hooks';
const Excel = ({ filePath }: { filePath: string }) => {
const { status, containerRef, error } = useFetchExcel(filePath);
return (
<div
id="excel"
ref={containerRef}
style={{ height: '100%', width: '100%' }}
>
{status || <FileError>{error}</FileError>}
</div>
);
};
export default Excel;

View File

@ -1,4 +0,0 @@
.errorWrapper {
width: 100%;
height: 100%;
}

View File

@ -1,18 +1,18 @@
import { Alert, Flex } from 'antd';
import { useTranslate } from '@/hooks/common-hooks';
import React from 'react';
import styles from './index.less';
const FileError = ({ children }: React.PropsWithChildren) => {
const { t } = useTranslate('fileManager');
return (
<Flex align="center" justify="center" className={styles.errorWrapper}>
<Alert
type="error"
message={<h2>{children || t('fileError')}</h2>}
></Alert>
</Flex>
<div className="flex items-center justify-center min-h-screen">
<div className="bg-state-error-5 border border-state-error rounded-lg p-4 shadow-sm">
<div className="flex ml-3">
<div className="text-white font-medium">
{children || t('fileError')}
</div>
</div>
</div>
</div>
);
};

View File

@ -1,109 +0,0 @@
import { Authorization } from '@/constants/authorization';
import { getAuthorization } from '@/utils/authorization-util';
import jsPreviewExcel from '@js-preview/excel';
import axios from 'axios';
import mammoth from 'mammoth';
import { useCallback, useEffect, useRef, useState } from 'react';
export const useCatchError = (api: string) => {
const [error, setError] = useState('');
const fetchDocument = useCallback(async () => {
const ret = await axios.get(api);
const { data } = ret;
if (!(data instanceof ArrayBuffer) && data.code !== 0) {
setError(data.message);
}
return ret;
}, [api]);
useEffect(() => {
fetchDocument();
}, [fetchDocument]);
return { fetchDocument, error };
};
export const useFetchDocument = () => {
const fetchDocument = useCallback(async (api: string) => {
const ret = await axios.get(api, {
headers: {
[Authorization]: getAuthorization(),
},
responseType: 'arraybuffer',
});
return ret;
}, []);
return { fetchDocument };
};
export const useFetchExcel = (filePath: string) => {
const [status, setStatus] = useState(true);
const { fetchDocument } = useFetchDocument();
const containerRef = useRef<HTMLDivElement>(null);
const { error } = useCatchError(filePath);
const fetchDocumentAsync = useCallback(async () => {
let myExcelPreviewer;
if (containerRef.current) {
myExcelPreviewer = jsPreviewExcel.init(containerRef.current);
}
const jsonFile = await fetchDocument(filePath);
myExcelPreviewer
?.preview(jsonFile.data)
.then(() => {
console.log('succeed');
setStatus(true);
})
.catch((e) => {
console.warn('failed', e);
myExcelPreviewer.destroy();
setStatus(false);
});
}, [filePath, fetchDocument]);
useEffect(() => {
fetchDocumentAsync();
}, [fetchDocumentAsync]);
return { status, containerRef, error };
};
export const useFetchDocx = (filePath: string) => {
const [succeed, setSucceed] = useState(true);
const [error, setError] = useState<string>();
const { fetchDocument } = useFetchDocument();
const containerRef = useRef<HTMLDivElement>(null);
const fetchDocumentAsync = useCallback(async () => {
try {
const jsonFile = await fetchDocument(filePath);
mammoth
.convertToHtml(
{ arrayBuffer: jsonFile.data },
{ includeDefaultStyleMap: true },
)
.then((result) => {
setSucceed(true);
const docEl = document.createElement('div');
docEl.className = 'document-container';
docEl.innerHTML = result.value;
const container = containerRef.current;
if (container) {
container.innerHTML = docEl.outerHTML;
}
})
.catch(() => {
setSucceed(false);
});
} catch (error: any) {
setError(error.toString());
}
}, [filePath, fetchDocument]);
useEffect(() => {
fetchDocumentAsync();
}, [fetchDocumentAsync]);
return { succeed, containerRef, error };
};

View File

@ -1,16 +1,22 @@
import { Images } from '@/constants/common';
import { api_host } from '@/utils/api';
import { Flex } from 'antd';
// import { Flex } from 'antd';
import { useParams, useSearchParams } from 'umi';
import Docx from './docx';
import Excel from './excel';
import Image from './image';
import Md from './md';
import Pdf from './pdf';
import Text from './text';
// import Docx from './docx';
// import Excel from './excel';
// import Image from './image';
// import Md from './md';
// import Pdf from './pdf';
// import Text from './text';
import { DocPreviewer } from '@/components/document-preview/doc-preview';
import { ExcelCsvPreviewer } from '@/components/document-preview/excel-preview';
import { ImagePreviewer } from '@/components/document-preview/image-preview';
import Md from '@/components/document-preview/md';
import PdfPreview from '@/components/document-preview/pdf-preview';
import { TxtPreviewer } from '@/components/document-preview/txt-preview';
import { previewHtmlFile } from '@/utils/file-util';
import styles from './index.less';
// import styles from './index.less';
// TODO: The interface returns an incorrect content-type for the SVG.
@ -20,6 +26,7 @@ const DocumentViewer = () => {
const ext = currentQueryParameters.get('ext');
const prefix = currentQueryParameters.get('prefix');
const api = `${api_host}/${prefix || 'file'}/get/${documentId}`;
// request.head
if (ext === 'html' && documentId) {
previewHtmlFile(documentId);
@ -27,19 +34,24 @@ const DocumentViewer = () => {
}
return (
<section className={styles.viewerWrapper}>
<section className="w-full h-full">
{Images.includes(ext!) && (
<Flex className={styles.image} align="center" justify="center">
<Image src={api} preview={false}></Image>
</Flex>
<div className="flex w-full h-full items-center justify-center">
{/* <Image src={api} preview={false}></Image> */}
<ImagePreviewer className="w-full !h-dvh p-5" url={api} />
</div>
)}
{ext === 'md' && <Md filePath={api}></Md>}
{ext === 'txt' && <Text filePath={api}></Text>}
{ext === 'md' && <Md url={api} className="!h-dvh p-5"></Md>}
{ext === 'txt' && <TxtPreviewer url={api}></TxtPreviewer>}
{ext === 'pdf' && <Pdf url={api}></Pdf>}
{(ext === 'xlsx' || ext === 'xls') && <Excel filePath={api}></Excel>}
{ext === 'pdf' && (
<PdfPreview url={api} className="!h-dvh p-5"></PdfPreview>
)}
{(ext === 'xlsx' || ext === 'xls') && (
<ExcelCsvPreviewer url={api}></ExcelCsvPreviewer>
)}
{ext === 'docx' && <Docx filePath={api}></Docx>}
{ext === 'docx' && <DocPreviewer url={api}></DocPreviewer>}
</section>
);
};

View File

@ -1,34 +0,0 @@
import React, { useEffect, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import FileError from '../file-error';
interface MdProps {
filePath: string;
}
const Md: React.FC<MdProps> = ({ filePath }) => {
const [content, setContent] = useState<string>('');
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setError(null);
fetch(filePath)
.then((res) => {
if (!res.ok) throw new Error('Failed to fetch markdown file');
return res.text();
})
.then((text) => setContent(text))
.catch((err) => setError(err.message));
}, [filePath]);
if (error) return <FileError>{error}</FileError>;
return (
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
<ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
</div>
);
};
export default Md;

View File

@ -1,32 +0,0 @@
import React, { useEffect, useState } from 'react';
import FileError from '../file-error';
interface TxtProps {
filePath: string;
}
const Md: React.FC<TxtProps> = ({ filePath }) => {
const [content, setContent] = useState<string>('');
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setError(null);
fetch(filePath)
.then((res) => {
if (!res.ok) throw new Error('Failed to fetch text file');
return res.text();
})
.then((text) => setContent(text))
.catch((err) => setError(err.message));
}, [filePath]);
if (error) return <FileError>{error}</FileError>;
return (
<div style={{ padding: 24, height: '100vh', overflow: 'scroll' }}>
{content}
</div>
);
};
export default Md;