[MM-47083] Migrate "components/pdf_preview.jsx" and tests to Typescript (#25211)

Этот коммит содержится в:
Sudhanva-Nadiger
2024-01-02 16:03:42 +05:30
коммит произвёл GitHub
родитель b4ee90afca
Коммит 64232a106e
6 изменённых файлов: 91 добавлений и 45 удалений

Просмотреть файл

@@ -12,7 +12,20 @@ exports[`component/PDFPreview should match snapshot, not successful 1`] = `
<Connect(FileInfoPreview)
fileInfo={
Object {
"archived": false,
"clientId": "client_id",
"create_at": 1,
"delete_at": 1,
"extension": "pdf",
"has_preview_image": true,
"height": 200,
"id": "file_info_id",
"mime_type": "mime_type",
"name": "name",
"size": 1,
"update_at": 1,
"user_id": "user_id",
"width": 350,
}
}
fileUrl="https://pre-release.mattermost.com/api/v4/files/ips59w4w9jnfbrs3o94m1dbdie"

Просмотреть файл

@@ -6,6 +6,8 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {useDispatch, useSelector} from 'react-redux';
import type {FileInfo} from '@mattermost/types/files';
import {closeModal} from 'actions/views/modals';
import {isModalOpen} from 'selectors/views/modals';
@@ -70,8 +72,8 @@ function CloudInvoicePreview(props: Props) {
extension: 'pdf',
size: 0,
name: '',
}}
fileUrl={props.url}
} as FileInfo}
fileUrl={props.url ?? ''}
scale={1.4}
handleBgClose={() => {}}
/>

Просмотреть файл

@@ -15,6 +15,7 @@ import AudioVideoPreview from 'components/audio_video_preview';
import CodePreview from 'components/code_preview';
import FileInfoPreview from 'components/file_info_preview';
import LoadingImagePreview from 'components/loading_image_preview';
import type {Props as PDFPreviewComponentProps} from 'components/pdf_preview';
import Constants, {FileTypes, ZoomSettings} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
@@ -31,7 +32,7 @@ import type {LinkInfo} from './types';
import './file_preview_modal.scss';
const PDFPreview = React.lazy(() => import('components/pdf_preview'));
const PDFPreview = React.lazy<React.ComponentType<PDFPreviewComponentProps>>(() => import('components/pdf_preview'));
const KeyCodes = Constants.KeyCodes;
@@ -326,7 +327,7 @@ export default class FilePreviewModal extends React.PureComponent<Props, State>
>
<React.Suspense fallback={null}>
<PDFPreview
fileInfo={fileInfo}
fileInfo={fileInfo as FileInfo}
fileUrl={fileUrl}
scale={this.state.scale[this.state.imageIndex]}
handleBgClose={this.handleBgClose}

Просмотреть файл

@@ -2,23 +2,27 @@
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import type {PDFDocumentProxy, PDFPageProxy} from 'pdfjs-dist';
import React from 'react';
import PDFPreview from 'components/pdf_preview.jsx';
import PDFPreview from 'components/pdf_preview';
import type {Props} from 'components/pdf_preview';
import {TestHelper} from 'utils/test_helper';
jest.mock('pdfjs-dist', () => ({
getDocument: () => Promise.resolve({
numPages: 3,
getPage: (i) => Promise.resolve({
getPage: (i: number) => Promise.resolve({
pageIndex: i,
getContext: (s) => Promise.resolve({s}),
getContext: (s: string) => Promise.resolve({s}),
}),
}),
}));
describe('component/PDFPreview', () => {
const requiredProps = {
fileInfo: {extension: 'pdf'},
const requiredProps: Props = {
fileInfo: TestHelper.getFileInfoMock({extension: 'pdf'}),
fileUrl: 'https://pre-release.mattermost.com/api/v4/files/ips59w4w9jnfbrs3o94m1dbdie',
scale: 1,
handleBgClose: jest.fn(),
@@ -40,7 +44,7 @@ describe('component/PDFPreview', () => {
});
test('should update state with new value from props when prop changes', () => {
const wrapper = shallow(
const wrapper = shallow<PDFPreview>(
<PDFPreview {...requiredProps}/>,
);
const newFileUrl = 'https://some-new-url';
@@ -51,19 +55,22 @@ describe('component/PDFPreview', () => {
});
test('should return correct state when onDocumentLoad is called', () => {
const wrapper = shallow(
const wrapper = shallow<PDFPreview>(
<PDFPreview {...requiredProps}/>,
);
let pdf = {numPages: 0};
let pdf = {numPages: 0} as PDFDocumentProxy;
wrapper.instance().onDocumentLoad(pdf);
expect(wrapper.state('pdf')).toEqual(pdf);
expect(wrapper.state('numPages')).toEqual(pdf.numPages);
pdf = {
numPages: 100,
getPage: (i) => Promise.resolve(i),
};
getPage: async (i) => {
const page = {pageNumber: i} as PDFPageProxy;
return Promise.resolve(page);
},
} as PDFDocumentProxy;
wrapper.instance().onDocumentLoad(pdf);
expect(wrapper.state('pdf')).toEqual(pdf);
expect(wrapper.state('numPages')).toEqual(pdf.numPages);

Просмотреть файл

@@ -2,9 +2,12 @@
// See LICENSE.txt for license information.
import debounce from 'lodash/debounce';
import PropTypes from 'prop-types';
import type {PDFDocumentProxy, PDFPageProxy} from 'pdfjs-dist';
import type {RenderParameters} from 'pdfjs-dist/types/src/display/api';
import React from 'react';
import type {FileInfo} from '@mattermost/types/files';
import {getFileDownloadUrl} from 'mattermost-redux/utils/file_utils';
import FileInfoPreview from 'components/file_info_preview';
@@ -14,23 +17,38 @@ import {getSiteURL} from 'utils/url';
const INITIAL_RENDERED_PAGES = 3;
export default class PDFPreview extends React.PureComponent {
static propTypes = {
export type Props = {
/**
/**
* Compare file types
*/
fileInfo: PropTypes.object.isRequired,
*/
fileInfo: FileInfo;
/**
/**
* URL of pdf file to output and compare to update props url
*/
fileUrl: PropTypes.string.isRequired,
scale: PropTypes.number.isRequired,
handleBgClose: PropTypes.func.isRequired,
};
*/
fileUrl: string;
scale: number;
handleBgClose: (e: React.MouseEvent<Element, MouseEvent>) => void;
}
constructor(props) {
type State = {
pdf: PDFDocumentProxy | null;
pdfPages: Record<number, PDFPageProxy>;
pdfPagesLoaded: Record<number, boolean>;
numPages: number;
loading: boolean;
success: boolean;
prevFileUrl: string;
}
export default class PDFPreview extends React.PureComponent<Props, State> {
public pdfPagesRendered: Record<number, boolean>;
public container: React.RefObject<HTMLDivElement>;
public parentNode: HTMLElement|null = null;
public pdfCanvasRef: {[key: string]: React.RefObject<HTMLCanvasElement>} = {};
constructor(props: Props) {
super(props);
this.pdfPagesRendered = {};
@@ -38,11 +56,12 @@ export default class PDFPreview extends React.PureComponent {
this.state = {
pdf: null,
pdfPages: {},
pdfPages: [],
pdfPagesLoaded: {},
numPages: 0,
loading: true,
success: false,
prevFileUrl: '',
};
}
@@ -50,7 +69,7 @@ export default class PDFPreview extends React.PureComponent {
this.getPdfDocument();
if (this.container.current) {
this.parentNode = this.container.current.parentElement;
this.parentNode.addEventListener('scroll', this.handleScroll);
this.parentNode?.addEventListener('scroll', this.handleScroll);
}
}
@@ -60,7 +79,7 @@ export default class PDFPreview extends React.PureComponent {
}
}
static getDerivedStateFromProps(props, state) {
static getDerivedStateFromProps(props: Props, state: State) {
if (props.fileUrl !== state.prevFileUrl) {
return {
pdf: null,
@@ -75,7 +94,7 @@ export default class PDFPreview extends React.PureComponent {
return null;
}
componentDidUpdate(prevProps, prevState) {
componentDidUpdate(prevProps: Props, prevState: State) {
if (this.props.fileUrl !== prevProps.fileUrl) {
this.getPdfDocument();
this.pdfPagesRendered = {};
@@ -96,16 +115,16 @@ export default class PDFPreview extends React.PureComponent {
}
}
downloadFile = (e) => {
downloadFile = (e: React.FormEvent) => {
const fileDownloadUrl = this.props.fileInfo.link || getFileDownloadUrl(this.props.fileInfo.id);
e.preventDefault();
window.location.href = fileDownloadUrl;
};
isInViewport = (page) => {
isInViewport = (page: Element) => {
const bounding = page.getBoundingClientRect();
const viewportTop = this.container.current.scrollTop;
const viewportBottom = viewportTop + this.container.current.parentElement.clientHeight;
const viewportTop = this.container.current?.scrollTop ?? 0;
const viewportBottom = viewportTop + (this.parentNode?.clientHeight ?? 0);
return (
(bounding.top >= viewportTop && bounding.top <= viewportBottom) ||
(bounding.bottom >= viewportTop && bounding.bottom <= viewportBottom) ||
@@ -113,8 +132,8 @@ export default class PDFPreview extends React.PureComponent {
);
};
renderPDFPage = async (pageIndex) => {
const canvas = this[`pdfCanvasRef-${pageIndex}`].current;
renderPDFPage = async (pageIndex: number) => {
const canvas = this.pdfCanvasRef[`pdfCanvasRef-${pageIndex}`].current;
if (!canvas) {
// Refs are undefined when testing
return;
@@ -130,16 +149,16 @@ export default class PDFPreview extends React.PureComponent {
return;
}
const page = await this.loadPage(this.state.pdf, pageIndex);
const page = await this.loadPage(this.state.pdf!, pageIndex);
const context = canvas.getContext('2d');
const viewport = page.getViewport({scale: this.props.scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
canvasContext: context as object,
viewport,
};
} as RenderParameters;
await page.render(renderContext).promise;
this.pdfPagesRendered[pageIndex] = true;
@@ -162,20 +181,20 @@ export default class PDFPreview extends React.PureComponent {
}
};
onDocumentLoad = (pdf) => {
onDocumentLoad = (pdf: PDFDocumentProxy) => {
this.setState({pdf, numPages: pdf.numPages});
for (let i = 0; i < pdf.numPages; i++) {
this[`pdfCanvasRef-${i}`] = React.createRef();
this.pdfCanvasRef[`pdfCanvasRef-${i}`] = React.createRef();
}
this.setState({loading: false, success: true});
};
onDocumentLoadError = (reason) => {
onDocumentLoadError = (reason: string) => {
console.log('Unable to load PDF preview: ' + reason); //eslint-disable-line no-console
this.setState({loading: false, success: false});
};
loadPage = async (pdf, pageIndex) => {
loadPage = async (pdf: PDFDocumentProxy, pageIndex: number) => {
if (this.state.pdfPagesLoaded[pageIndex]) {
return this.state.pdfPages[pageIndex];
}
@@ -225,7 +244,7 @@ export default class PDFPreview extends React.PureComponent {
for (let i = 0; i < this.state.numPages; i++) {
pdfCanvases.push(
<canvas
ref={this[`pdfCanvasRef-${i}`]}
ref={this.pdfCanvasRef[`pdfCanvasRef-${i}`]}
key={'previewpdfcanvas' + i}
/>,
);

4
webapp/channels/src/types/external/pdf-worker-entry.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,4 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
declare module 'pdfjs-dist/build/pdf.worker.entry.js';