diff options
author | Joram Wilander <jwawilander@gmail.com> | 2016-08-18 18:38:38 -0400 |
---|---|---|
committer | Corey Hulen <corey@hulen.com> | 2016-08-18 14:38:38 -0800 |
commit | 3289c856130c4d1956dda9229fb3c6a060655b1a (patch) | |
tree | 1c3f180c9ef1a8c5a8146e63c68e82cdf3e175d0 | |
parent | ed6b69aab3136b2a5bcddbab77659640cd4d6534 (diff) | |
download | chat-3289c856130c4d1956dda9229fb3c6a060655b1a.tar.gz chat-3289c856130c4d1956dda9229fb3c6a060655b1a.tar.bz2 chat-3289c856130c4d1956dda9229fb3c6a060655b1a.zip |
PLT-3642 Add PDF previewer (#3812)
* Added a PDF previewer
* PLT-3900 - Improving UI for the pdf max pages (#3800)
-rw-r--r-- | webapp/components/pdf_preview.jsx | 189 | ||||
-rw-r--r-- | webapp/components/view_image.jsx | 35 | ||||
-rw-r--r-- | webapp/i18n/en.json | 1 | ||||
-rw-r--r-- | webapp/package.json | 1 | ||||
-rw-r--r-- | webapp/sass/components/_files.scss | 24 | ||||
-rw-r--r-- | webapp/sass/responsive/_mobile.scss | 3 |
6 files changed, 241 insertions, 12 deletions
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 ( + <div className='view-image__loading'> + <img + className='loader-image' + src={loadingGif} + /> + </div> + ); + } + + if (!this.state.success) { + return ( + <FileInfoPreview + filename={this.props.filename} + fileUrl={this.props.fileUrl} + fileInfo={this.props.fileInfo} + formatMessage={this.props.formatMessage} + /> + ); + } + + const pdfCanvases = []; + for (let i = 0; i < this.state.numPages; i++) { + pdfCanvases.push( + <canvas + ref={'pdfCanvas' + i} + key={'previewpdfcanvas' + i} + /> + ); + + if (i < this.state.numPages - 1 && this.state.numPages > 1) { + pdfCanvases.push( + <div className='pdf-preview-spacer'/> + ); + } + } + + if (this.state.pdf.numPages > MAX_PDF_PAGES) { + pdfCanvases.push( + <div className='pdf-max-pages'> + <FormattedMessage + id='pdf_preview.max_pages' + defaultMessage='PDF previews only show the first five pages.' + /> + </div> + ); + } + + return ( + <div className='post-code'> + {pdfCanvases} + </div> + ); + } +} + +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 = ( + <PDFPreview + filename={filename} + fileUrl={fileUrl} + fileInfo={fileInfo} + formatMessage={this.props.intl.formatMessage} + /> + ); } else if (CodePreview.support(filename)) { content = ( <CodePreview diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index d8a5dfc6b..e7f744941 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -1477,6 +1477,7 @@ "navbar_dropdown.teamSettings": "Team Settings", "navbar_dropdown.viewMembers": "View Members", "notification.dm": "Direct Message", + "pdf_preview.max_pages": "PDF previews only show the first five pages.", "password_form.change": "Change my password", "password_form.click": "Click <a href={url}>here</a> to log in.", "password_form.enter": "Enter a new password for your {siteName} account.", diff --git a/webapp/package.json b/webapp/package.json index dce79447a..2ad477f4d 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -19,6 +19,7 @@ "marked": "mattermost/marked#69736482dbad685c398a5eec33a59b5ab06057ac", "match-at": "0.1.0", "object-assign": "4.1.0", + "pdfjs-dist": "1.5.374", "perfect-scrollbar": "0.6.12", "react": "15.2.1", "react-addons-pure-render-mixin": "15.2.1", diff --git a/webapp/sass/components/_files.scss b/webapp/sass/components/_files.scss index 9a65693da..2239bdd1b 100644 --- a/webapp/sass/components/_files.scss +++ b/webapp/sass/components/_files.scss @@ -134,6 +134,30 @@ } } +.pdf-preview-spacer { + height: 5px; +} + +.pdf-max-pages { + bottom: 0; + background: $white; + left: 0; + position: relative; + width: 100%; + + span { + @include border-radius(3px); + background: alpha-color($black, .8); + bottom: 15px; + color: $white; + display: inline-block; + height: 3em; + line-height: 3em; + padding: 0 1.5em; + position: relative; + } +} + .post-image__column { border: 1px solid alpha-color($black, .2); float: left; diff --git a/webapp/sass/responsive/_mobile.scss b/webapp/sass/responsive/_mobile.scss index bda11dc2c..5505aa353 100644 --- a/webapp/sass/responsive/_mobile.scss +++ b/webapp/sass/responsive/_mobile.scss @@ -393,7 +393,7 @@ padding: 5px; } - .image-wrapper { + .modal-image__wrapper { > div { font-size: 12px; min-width: 250px; @@ -406,6 +406,7 @@ .modal-button-bar { @include opacity(1); + padding-right: 10px; } } |