From 3289c856130c4d1956dda9229fb3c6a060655b1a Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 18 Aug 2016 18:38:38 -0400 Subject: PLT-3642 Add PDF previewer (#3812) * Added a PDF previewer * PLT-3900 - Improving UI for the pdf max pages (#3800) --- webapp/components/pdf_preview.jsx | 189 ++++++++++++++++++++++++++++++++++++++ webapp/components/view_image.jsx | 35 ++++--- 2 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 webapp/components/pdf_preview.jsx (limited to 'webapp/components') diff --git a/webapp/components/pdf_preview.jsx b/webapp/components/pdf_preview.jsx new file mode 100644 index 000000000..fd1ca7758 --- /dev/null +++ b/webapp/components/pdf_preview.jsx @@ -0,0 +1,189 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import FileInfoPreview from './file_info_preview.jsx'; + +import * as Utils from 'utils/utils.jsx'; + +import loadingGif from 'images/load.gif'; + +import React from 'react'; +import PDFJS from 'pdfjs-dist'; +import {FormattedMessage} from 'react-intl'; + +const MAX_PDF_PAGES = 5; + +export default class PDFPreview extends React.Component { + constructor(props) { + super(props); + + this.updateStateFromProps = this.updateStateFromProps.bind(this); + this.onDocumentLoad = this.onDocumentLoad.bind(this); + this.onPageLoad = this.onPageLoad.bind(this); + this.renderPDFPage = this.renderPDFPage.bind(this); + + this.pdfPagesRendered = {}; + + this.state = { + pdf: null, + pdfPages: {}, + pdfPagesLoaded: {}, + numPages: 0, + loading: true, + success: false + }; + } + + componentDidMount() { + this.updateStateFromProps(this.props); + } + + componentWillReceiveProps(nextProps) { + if (this.props.fileUrl !== nextProps.fileUrl) { + this.updateStateFromProps(nextProps); + this.pdfPagesRendered = {}; + } + } + + componentDidUpdate() { + if (this.state.success) { + for (let i = 0; i < this.state.numPages; i++) { + this.renderPDFPage(i); + } + } + } + + renderPDFPage(pageIndex) { + if (this.pdfPagesRendered[pageIndex] || !this.state.pdfPagesLoaded[pageIndex]) { + return; + } + + const canvas = this.refs['pdfCanvas' + pageIndex]; + const context = canvas.getContext('2d'); + const viewport = this.state.pdfPages[pageIndex].getViewport(1); + + canvas.height = viewport.height; + canvas.width = viewport.width; + + const renderContext = { + canvasContext: context, + viewport + }; + + this.state.pdfPages[pageIndex].render(renderContext); + this.pdfPagesRendered[pageIndex] = true; + } + + updateStateFromProps(props) { + this.setState({ + pdf: null, + pdfPages: {}, + pdfPagesLoaded: {}, + numPages: 0, + loading: true, + success: false + }); + + PDFJS.getDocument(window.mm_config.SiteURL + props.fileUrl).then(this.onDocumentLoad); + } + + onDocumentLoad(pdf) { + const numPages = pdf.numPages <= MAX_PDF_PAGES ? pdf.numPages : MAX_PDF_PAGES; + this.setState({pdf, numPages}); + for (let i = 1; i <= pdf.numPages; i++) { + pdf.getPage(i).then(this.onPageLoad); + } + } + + onPageLoad(page) { + const pdfPages = Object.assign({}, this.state.pdfPages); + pdfPages[page.pageIndex] = page; + + const pdfPagesLoaded = Object.assign({}, this.state.pdfPagesLoaded); + pdfPagesLoaded[page.pageIndex] = true; + + this.setState({pdfPages, pdfPagesLoaded}); + + if (page.pageIndex === 0) { + this.setState({success: true, loading: false}); + } + } + + static support(filename) { + const fileInfo = Utils.splitFileLocation(filename); + const ext = fileInfo.ext; + if (!ext) { + return false; + } + + if (ext === 'pdf') { + return true; + } + + return false; + } + + render() { + if (this.state.loading) { + return ( +
+ +
+ ); + } + + if (!this.state.success) { + return ( + + ); + } + + const pdfCanvases = []; + for (let i = 0; i < this.state.numPages; i++) { + pdfCanvases.push( + + ); + + if (i < this.state.numPages - 1 && this.state.numPages > 1) { + pdfCanvases.push( +
+ ); + } + } + + if (this.state.pdf.numPages > MAX_PDF_PAGES) { + pdfCanvases.push( +
+ +
+ ); + } + + return ( +
+ {pdfCanvases} +
+ ); + } +} + +PDFPreview.propTypes = { + filename: React.PropTypes.string.isRequired, + fileUrl: React.PropTypes.string.isRequired, + fileInfo: React.PropTypes.object.isRequired, + formatMessage: React.PropTypes.func.isRequired +}; diff --git a/webapp/components/view_image.jsx b/webapp/components/view_image.jsx index 7b827ac0e..c9f558725 100644 --- a/webapp/components/view_image.jsx +++ b/webapp/components/view_image.jsx @@ -1,23 +1,29 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -import $ from 'jquery'; -import * as AsyncClient from 'utils/async_client.jsx'; -import * as GlobalActions from 'actions/global_actions.jsx'; -import * as Utils from 'utils/utils.jsx'; import AudioVideoPreview from './audio_video_preview.jsx'; -import Constants from 'utils/constants.jsx'; import CodePreview from './code_preview.jsx'; +import PDFPreview from './pdf_preview.jsx'; import FileInfoPreview from './file_info_preview.jsx'; -import FileStore from 'stores/file_store.jsx'; import ViewImagePopoverBar from './view_image_popover_bar.jsx'; -import loadingGif from 'images/load.gif'; -import {intlShape, injectIntl, defineMessages} from 'react-intl'; +import * as GlobalActions from 'actions/global_actions.jsx'; -import {Modal} from 'react-bootstrap'; +import FileStore from 'stores/file_store.jsx'; + +import * as AsyncClient from 'utils/async_client.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import Constants from 'utils/constants.jsx'; const KeyCodes = Constants.KeyCodes; +import $ from 'jquery'; +import React from 'react'; +import {intlShape, injectIntl, defineMessages} from 'react-intl'; +import {Modal} from 'react-bootstrap'; + +import loadingGif from 'images/load.gif'; + const holders = defineMessages({ loading: { id: 'view_image.loading', @@ -25,8 +31,6 @@ const holders = defineMessages({ } }); -import React from 'react'; - class ViewImageModal extends React.Component { constructor(props) { super(props); @@ -241,6 +245,15 @@ class ViewImageModal extends React.Component { formatMessage={this.props.intl.formatMessage} /> ); + } else if (PDFPreview.support(filename)) { + content = ( + + ); } else if (CodePreview.support(filename)) { content = (