[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)
|
<Connect(FileInfoPreview)
|
||||||
fileInfo={
|
fileInfo={
|
||||||
Object {
|
Object {
|
||||||
|
"archived": false,
|
||||||
|
"clientId": "client_id",
|
||||||
|
"create_at": 1,
|
||||||
|
"delete_at": 1,
|
||||||
"extension": "pdf",
|
"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"
|
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 {FormattedMessage} from 'react-intl';
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
import {useDispatch, useSelector} from 'react-redux';
|
||||||
|
|
||||||
|
import type {FileInfo} from '@mattermost/types/files';
|
||||||
|
|
||||||
import {closeModal} from 'actions/views/modals';
|
import {closeModal} from 'actions/views/modals';
|
||||||
import {isModalOpen} from 'selectors/views/modals';
|
import {isModalOpen} from 'selectors/views/modals';
|
||||||
|
|
||||||
@@ -70,8 +72,8 @@ function CloudInvoicePreview(props: Props) {
|
|||||||
extension: 'pdf',
|
extension: 'pdf',
|
||||||
size: 0,
|
size: 0,
|
||||||
name: '',
|
name: '',
|
||||||
}}
|
} as FileInfo}
|
||||||
fileUrl={props.url}
|
fileUrl={props.url ?? ''}
|
||||||
scale={1.4}
|
scale={1.4}
|
||||||
handleBgClose={() => {}}
|
handleBgClose={() => {}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import AudioVideoPreview from 'components/audio_video_preview';
|
|||||||
import CodePreview from 'components/code_preview';
|
import CodePreview from 'components/code_preview';
|
||||||
import FileInfoPreview from 'components/file_info_preview';
|
import FileInfoPreview from 'components/file_info_preview';
|
||||||
import LoadingImagePreview from 'components/loading_image_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 Constants, {FileTypes, ZoomSettings} from 'utils/constants';
|
||||||
import * as Keyboard from 'utils/keyboard';
|
import * as Keyboard from 'utils/keyboard';
|
||||||
@@ -31,7 +32,7 @@ import type {LinkInfo} from './types';
|
|||||||
|
|
||||||
import './file_preview_modal.scss';
|
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;
|
const KeyCodes = Constants.KeyCodes;
|
||||||
|
|
||||||
@@ -326,7 +327,7 @@ export default class FilePreviewModal extends React.PureComponent<Props, State>
|
|||||||
>
|
>
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null}>
|
||||||
<PDFPreview
|
<PDFPreview
|
||||||
fileInfo={fileInfo}
|
fileInfo={fileInfo as FileInfo}
|
||||||
fileUrl={fileUrl}
|
fileUrl={fileUrl}
|
||||||
scale={this.state.scale[this.state.imageIndex]}
|
scale={this.state.scale[this.state.imageIndex]}
|
||||||
handleBgClose={this.handleBgClose}
|
handleBgClose={this.handleBgClose}
|
||||||
|
|||||||
@@ -2,23 +2,27 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {shallow} from 'enzyme';
|
import {shallow} from 'enzyme';
|
||||||
|
import type {PDFDocumentProxy, PDFPageProxy} from 'pdfjs-dist';
|
||||||
import React from 'react';
|
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', () => ({
|
jest.mock('pdfjs-dist', () => ({
|
||||||
getDocument: () => Promise.resolve({
|
getDocument: () => Promise.resolve({
|
||||||
numPages: 3,
|
numPages: 3,
|
||||||
getPage: (i) => Promise.resolve({
|
getPage: (i: number) => Promise.resolve({
|
||||||
pageIndex: i,
|
pageIndex: i,
|
||||||
getContext: (s) => Promise.resolve({s}),
|
getContext: (s: string) => Promise.resolve({s}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('component/PDFPreview', () => {
|
describe('component/PDFPreview', () => {
|
||||||
const requiredProps = {
|
const requiredProps: Props = {
|
||||||
fileInfo: {extension: 'pdf'},
|
fileInfo: TestHelper.getFileInfoMock({extension: 'pdf'}),
|
||||||
fileUrl: 'https://pre-release.mattermost.com/api/v4/files/ips59w4w9jnfbrs3o94m1dbdie',
|
fileUrl: 'https://pre-release.mattermost.com/api/v4/files/ips59w4w9jnfbrs3o94m1dbdie',
|
||||||
scale: 1,
|
scale: 1,
|
||||||
handleBgClose: jest.fn(),
|
handleBgClose: jest.fn(),
|
||||||
@@ -40,7 +44,7 @@ describe('component/PDFPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should update state with new value from props when prop changes', () => {
|
test('should update state with new value from props when prop changes', () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow<PDFPreview>(
|
||||||
<PDFPreview {...requiredProps}/>,
|
<PDFPreview {...requiredProps}/>,
|
||||||
);
|
);
|
||||||
const newFileUrl = 'https://some-new-url';
|
const newFileUrl = 'https://some-new-url';
|
||||||
@@ -51,19 +55,22 @@ describe('component/PDFPreview', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should return correct state when onDocumentLoad is called', () => {
|
test('should return correct state when onDocumentLoad is called', () => {
|
||||||
const wrapper = shallow(
|
const wrapper = shallow<PDFPreview>(
|
||||||
<PDFPreview {...requiredProps}/>,
|
<PDFPreview {...requiredProps}/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
let pdf = {numPages: 0};
|
let pdf = {numPages: 0} as PDFDocumentProxy;
|
||||||
wrapper.instance().onDocumentLoad(pdf);
|
wrapper.instance().onDocumentLoad(pdf);
|
||||||
expect(wrapper.state('pdf')).toEqual(pdf);
|
expect(wrapper.state('pdf')).toEqual(pdf);
|
||||||
expect(wrapper.state('numPages')).toEqual(pdf.numPages);
|
expect(wrapper.state('numPages')).toEqual(pdf.numPages);
|
||||||
|
|
||||||
pdf = {
|
pdf = {
|
||||||
numPages: 100,
|
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);
|
wrapper.instance().onDocumentLoad(pdf);
|
||||||
expect(wrapper.state('pdf')).toEqual(pdf);
|
expect(wrapper.state('pdf')).toEqual(pdf);
|
||||||
expect(wrapper.state('numPages')).toEqual(pdf.numPages);
|
expect(wrapper.state('numPages')).toEqual(pdf.numPages);
|
||||||
@@ -2,9 +2,12 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import debounce from 'lodash/debounce';
|
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 React from 'react';
|
||||||
|
|
||||||
|
import type {FileInfo} from '@mattermost/types/files';
|
||||||
|
|
||||||
import {getFileDownloadUrl} from 'mattermost-redux/utils/file_utils';
|
import {getFileDownloadUrl} from 'mattermost-redux/utils/file_utils';
|
||||||
|
|
||||||
import FileInfoPreview from 'components/file_info_preview';
|
import FileInfoPreview from 'components/file_info_preview';
|
||||||
@@ -14,23 +17,38 @@ import {getSiteURL} from 'utils/url';
|
|||||||
|
|
||||||
const INITIAL_RENDERED_PAGES = 3;
|
const INITIAL_RENDERED_PAGES = 3;
|
||||||
|
|
||||||
export default class PDFPreview extends React.PureComponent {
|
export type Props = {
|
||||||
static propTypes = {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare file types
|
* Compare file types
|
||||||
*/
|
*/
|
||||||
fileInfo: PropTypes.object.isRequired,
|
fileInfo: FileInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* URL of pdf file to output and compare to update props url
|
* URL of pdf file to output and compare to update props url
|
||||||
*/
|
*/
|
||||||
fileUrl: PropTypes.string.isRequired,
|
fileUrl: string;
|
||||||
scale: PropTypes.number.isRequired,
|
scale: number;
|
||||||
handleBgClose: PropTypes.func.isRequired,
|
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);
|
super(props);
|
||||||
|
|
||||||
this.pdfPagesRendered = {};
|
this.pdfPagesRendered = {};
|
||||||
@@ -38,11 +56,12 @@ export default class PDFPreview extends React.PureComponent {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
pdf: null,
|
pdf: null,
|
||||||
pdfPages: {},
|
pdfPages: [],
|
||||||
pdfPagesLoaded: {},
|
pdfPagesLoaded: {},
|
||||||
numPages: 0,
|
numPages: 0,
|
||||||
loading: true,
|
loading: true,
|
||||||
success: false,
|
success: false,
|
||||||
|
prevFileUrl: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +69,7 @@ export default class PDFPreview extends React.PureComponent {
|
|||||||
this.getPdfDocument();
|
this.getPdfDocument();
|
||||||
if (this.container.current) {
|
if (this.container.current) {
|
||||||
this.parentNode = this.container.current.parentElement;
|
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) {
|
if (props.fileUrl !== state.prevFileUrl) {
|
||||||
return {
|
return {
|
||||||
pdf: null,
|
pdf: null,
|
||||||
@@ -75,7 +94,7 @@ export default class PDFPreview extends React.PureComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||||
if (this.props.fileUrl !== prevProps.fileUrl) {
|
if (this.props.fileUrl !== prevProps.fileUrl) {
|
||||||
this.getPdfDocument();
|
this.getPdfDocument();
|
||||||
this.pdfPagesRendered = {};
|
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);
|
const fileDownloadUrl = this.props.fileInfo.link || getFileDownloadUrl(this.props.fileInfo.id);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location.href = fileDownloadUrl;
|
window.location.href = fileDownloadUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
isInViewport = (page) => {
|
isInViewport = (page: Element) => {
|
||||||
const bounding = page.getBoundingClientRect();
|
const bounding = page.getBoundingClientRect();
|
||||||
const viewportTop = this.container.current.scrollTop;
|
const viewportTop = this.container.current?.scrollTop ?? 0;
|
||||||
const viewportBottom = viewportTop + this.container.current.parentElement.clientHeight;
|
const viewportBottom = viewportTop + (this.parentNode?.clientHeight ?? 0);
|
||||||
return (
|
return (
|
||||||
(bounding.top >= viewportTop && bounding.top <= viewportBottom) ||
|
(bounding.top >= viewportTop && bounding.top <= viewportBottom) ||
|
||||||
(bounding.bottom >= viewportTop && bounding.bottom <= viewportBottom) ||
|
(bounding.bottom >= viewportTop && bounding.bottom <= viewportBottom) ||
|
||||||
@@ -113,8 +132,8 @@ export default class PDFPreview extends React.PureComponent {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPDFPage = async (pageIndex) => {
|
renderPDFPage = async (pageIndex: number) => {
|
||||||
const canvas = this[`pdfCanvasRef-${pageIndex}`].current;
|
const canvas = this.pdfCanvasRef[`pdfCanvasRef-${pageIndex}`].current;
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
// Refs are undefined when testing
|
// Refs are undefined when testing
|
||||||
return;
|
return;
|
||||||
@@ -130,16 +149,16 @@ export default class PDFPreview extends React.PureComponent {
|
|||||||
return;
|
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 context = canvas.getContext('2d');
|
||||||
const viewport = page.getViewport({scale: this.props.scale});
|
const viewport = page.getViewport({scale: this.props.scale});
|
||||||
canvas.height = viewport.height;
|
canvas.height = viewport.height;
|
||||||
canvas.width = viewport.width;
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
const renderContext = {
|
const renderContext = {
|
||||||
canvasContext: context,
|
canvasContext: context as object,
|
||||||
viewport,
|
viewport,
|
||||||
};
|
} as RenderParameters;
|
||||||
|
|
||||||
await page.render(renderContext).promise;
|
await page.render(renderContext).promise;
|
||||||
this.pdfPagesRendered[pageIndex] = true;
|
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});
|
this.setState({pdf, numPages: pdf.numPages});
|
||||||
for (let i = 0; i < pdf.numPages; i++) {
|
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});
|
this.setState({loading: false, success: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
onDocumentLoadError = (reason) => {
|
onDocumentLoadError = (reason: string) => {
|
||||||
console.log('Unable to load PDF preview: ' + reason); //eslint-disable-line no-console
|
console.log('Unable to load PDF preview: ' + reason); //eslint-disable-line no-console
|
||||||
this.setState({loading: false, success: false});
|
this.setState({loading: false, success: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
loadPage = async (pdf, pageIndex) => {
|
loadPage = async (pdf: PDFDocumentProxy, pageIndex: number) => {
|
||||||
if (this.state.pdfPagesLoaded[pageIndex]) {
|
if (this.state.pdfPagesLoaded[pageIndex]) {
|
||||||
return this.state.pdfPages[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++) {
|
for (let i = 0; i < this.state.numPages; i++) {
|
||||||
pdfCanvases.push(
|
pdfCanvases.push(
|
||||||
<canvas
|
<canvas
|
||||||
ref={this[`pdfCanvasRef-${i}`]}
|
ref={this.pdfCanvasRef[`pdfCanvasRef-${i}`]}
|
||||||
key={'previewpdfcanvas' + 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