[MM-47083] Migrate "components/pdf_preview.jsx" and tests to Typescript (#25211)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
b4ee90afca
Коммит
64232a106e
@@ -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
поставляемый
Обычный файл
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';
|
||||
Ссылка в новой задаче
Block a user