Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: Preload for presentation viewer #12

Merged
merged 1 commit into from
Mar 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/webpack.common.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ module.exports = (language) => {
test: /\.(jpe?g|png|gif|woff2|woff)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
name: '[name].[ext]'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure images are not being created/repeated per locale.
It should be at a higher level so that all locales use the same image.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like we discussed, this image is small and was already placed in the locale repos previously.

},
exclude: [
path.resolve('src/third-party'),
Expand Down
5 changes: 2 additions & 3 deletions src/lib/Preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,8 @@ class Preview extends EventEmitter {
// Determining the viewer could throw an error
try {
file = cache.get(fileId);
loader = this.getLoader(file);
viewer = loader.determineViewer(file);

loader = file ? this.getLoader(file) : null;
viewer = loader ? loader.determineViewer(file) : null;
if (!viewer) {
return;
}
Expand Down
32 changes: 0 additions & 32 deletions src/lib/_common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -173,38 +173,6 @@ $header-height: 48px;
}
}

// Used for showing preload, aka Instant Preview
.bp-preload-wrapper {
bottom: 0;
left: 0;
margin: 0;
opacity: 1;
overflow-y: scroll;
position: absolute;
right: 0;
top: 0;
transition: opacity .7s;

&.bp-is-invisible {
opacity: 0;
visibility: visible;
}

.bp-preload {
background-color: $ffive;

.bp-theme-dark & {
background-color: $sunset-grey;
}
}

.bp-preload-content {
background: $white url('loadingIcon.gif') center no-repeat;
display: block;
margin: 15px auto 30px;
}
}

.bp-popup-modal {
left: 0;
margin: 0 auto;
Expand Down
3 changes: 2 additions & 1 deletion src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export const CLASS_BOX_PREVIEW_OVERLAY = 'bp-overlay';
export const CLASS_BOX_PREVIEW_OVERLAY_WRAPPER = 'bp-overlay-wrapper';
export const CLASS_BOX_PREVIEW_PRELOAD = 'bp-preload';
export const CLASS_BOX_PREVIEW_PRELOAD_CONTENT = 'bp-preload-content';
export const CLASS_BOX_PREVIEW_PRELOAD_WRAPPER = 'bp-preload-wrapper';
export const CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_DOCUMENT = 'bp-document-preload-wrapper';
export const CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_PRESENTATION = 'bp-presentation-preload-wrapper';
export const CLASS_BOX_PREVIEW_TOGGLE_OVERLAY = 'bp-toggle-overlay';
export const CLASS_BOX_PREVIEW_THEME_DARK = 'bp-theme-dark';
export const CLASS_FULLSCREEN = 'bp-is-fullscreen';
Expand Down
27 changes: 23 additions & 4 deletions src/lib/viewers/doc/DocBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,13 @@ class DocBase extends Base {
*/
prefetch({ assets = true, preload = true, content = true }) {
const { file, representation } = this.options;
const isWatermarked = file && file.watermark_info && file.watermark_info.is_watermarked;

if (assets) {
this.prefetchAssets(JS, CSS);
}

if (preload) {
if (preload && !isWatermarked) {
const preloadRep = getRepresentation(file, PRELOAD_REP_NAME);
if (preloadRep && this.isRepresentationReady(preloadRep)) {
const { url_template: template } = preloadRep.content;
Expand All @@ -142,7 +143,7 @@ class DocBase extends Base {
}
}

if (content && this.isRepresentationReady(representation)) {
if (content && !isWatermarked && this.isRepresentationReady(representation)) {
const { url_template: template } = representation.content;
get(this.createContentUrlWithAuthParams(template), 'any');
}
Expand All @@ -154,7 +155,23 @@ class DocBase extends Base {
* @return {void}
*/
showPreload() {
// no-op in base
const { file } = this.options;
const isWatermarked = file && file.watermark_info && file.watermark_info.is_watermarked;

// Don't show preload if there's a cached page since preloads are only for the 1st page
// Also don't show preloads for watermarked files
if (!this.preloader || isWatermarked || this.getCachedPage() !== 1) {
return;
}

const preloadRep = getRepresentation(file, PRELOAD_REP_NAME);
if (!preloadRep || !this.getViewerOption('preload')) {
return;
}

const { url_template: template } = preloadRep.content;
const preloadUrlWithAuth = this.createContentUrlWithAuthParams(template);
this.preloader.showPreload(preloadUrlWithAuth, this.containerEl);
}

/**
Expand All @@ -164,7 +181,9 @@ class DocBase extends Base {
* @return {void}
*/
hidePreload() {
// no-op in base
if (this.preloader) {
this.preloader.hidePreload();
}
}

/**
Expand Down
133 changes: 88 additions & 45 deletions src/lib/viewers/doc/DocPreloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import EventEmitter from 'events';
import {
CLASS_BOX_PREVIEW_PRELOAD,
CLASS_BOX_PREVIEW_PRELOAD_CONTENT,
CLASS_BOX_PREVIEW_PRELOAD_WRAPPER,
CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_DOCUMENT,
CLASS_INVISIBLE,
CLASS_PREVIEW_LOADED
} from '../../constants';
Expand All @@ -12,15 +12,26 @@ import { hideLoadingIndicator } from '../../ui';
const EXIF_COMMENT_TAG_NAME = 'UserComment'; // Read EXIF data from 'UserComment' tag
const EXIF_COMMENT_REGEX = /pdfWidth:([0-9.]+)pts,pdfHeight:([0-9.]+)pts,numPages:([0-9]+)/;

const PDF_UNIT_TO_CSS_PIXEL = 4 / 3; // PDF unit = 1/72 inch, CSS pixel = 1/92 inch
const PDFJS_CSS_UNITS = 96.0 / 72.0; // Should match CSS_UNITS in pdf_viewer.js
const PDFJS_MAX_AUTO_SCALE = 1.25; // Should match MAX_AUTO_SCALE in pdf_viewer.js
const PDFJS_WIDTH_PADDING_PX = 40; // Should match SCROLLBAR_PADDING in pdf_viewer.js
const PDFJS_HEIGHT_PADDING_PX = 5; // Should match VERTICAL_PADDING in pdf_viewer.js

const NUM_PAGES_DEFAULT = 2; // Default to 2 pages for preload if true number of pages cannot be read
const NUM_PAGES_MAX = 500; // Don't show more than 500 placeholder pages

const ACCEPTABLE_RATIO_DIFFERENCE = 0.025; // Acceptable difference in ratio of PDF dimensions to image dimensions

class DocPreloader extends EventEmitter {
/**
* [constructor]
*
* @return {DocPreloader} DocPreloader instance
*/
constructor() {
super();
this.wrapperClassName = CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_DOCUMENT;
}

/**
* Shows a preload of the document by showing the first page as an image. This should be called
Expand All @@ -42,7 +53,7 @@ class DocPreloader extends EventEmitter {
this.srcUrl = URL.createObjectURL(imgBlob);

this.wrapperEl = document.createElement('div');
this.wrapperEl.className = CLASS_BOX_PREVIEW_PRELOAD_WRAPPER;
this.wrapperEl.className = this.wrapperClassName;
this.wrapperEl.innerHTML = `
<div class="${CLASS_BOX_PREVIEW_PRELOAD} ${CLASS_INVISIBLE}">
<img class="${CLASS_BOX_PREVIEW_PRELOAD_CONTENT}" src="${this.srcUrl}" />
Expand All @@ -56,6 +67,40 @@ class DocPreloader extends EventEmitter {
});
}

/**
* Set scaled dimensions for the preload image and show.
*
* @param {number} scaledWidth - Width in pixels to scale preload to
* @param {number} scaledHeight - Height in pixels to scale preload to
* @param {number} numPages - Number of pages to show for preload
* @return {void}
*/
scaleAndShowPreload(scaledWidth, scaledHeight, numPages) {
if (this.checkDocumentLoaded()) {
return;
}

// Set image dimensions
setDimensions(this.imageEl, scaledWidth, scaledHeight);

// Add and scale correct number of placeholder elements
for (let i = 0; i < numPages - 1; i++) {
const placeholderEl = document.createElement('div');
placeholderEl.className = CLASS_BOX_PREVIEW_PRELOAD_CONTENT;
setDimensions(placeholderEl, scaledWidth, scaledHeight);
this.preloadEl.appendChild(placeholderEl);
}

// Hide the preview-level loading indicator
hideLoadingIndicator();

// Show preload element after content is properly sized
this.preloadEl.classList.remove(CLASS_INVISIBLE);

// Emit message that preload has occurred
this.emit('preload');
}

/**
* Hides the preload if it exists.
*
Expand Down Expand Up @@ -157,15 +202,48 @@ class DocPreloader extends EventEmitter {
const userComment = userCommentRaw.map((c) => String.fromCharCode(c)).join('');
const match = EXIF_COMMENT_REGEX.exec(userComment);

if (match && match.length === 4) {
resolve({
pdfWidth: parseInt(match[1], 10) * PDF_UNIT_TO_CSS_PIXEL,
pdfHeight: parseInt(match[2], 10) * PDF_UNIT_TO_CSS_PIXEL,
numPages: parseInt(match[3], 10)
});
} else {
// There should be 3 pieces of metadata: PDF width, PDF height, and num pages
if (!match || match.length !== 4) {
reject('No valid EXIF data found');
return;
}

// Convert PDF Units to CSS Pixels
let pdfWidth = parseInt(match[1], 10) * PDFJS_CSS_UNITS;
let pdfHeight = parseInt(match[2], 10) * PDFJS_CSS_UNITS;
const numPages = parseInt(match[3], 10);

// Validate number of pages
if (numPages <= 0) {
reject('EXIF num pages data is invalid');
return;
}

// Validate PDF width and height by comparing ratio to preload image dimension ratio
const pdfRatio = pdfWidth / pdfHeight;
const imageRatio = imageEl.naturalWidth / imageEl.naturalHeight;

if (Math.abs(pdfRatio - imageRatio) > ACCEPTABLE_RATIO_DIFFERENCE) {
const rotatedPdfRatio = pdfHeight / pdfWidth;

// Check if ratio is valid after height and width are swapped since PDF may be rotated
if (Math.abs(rotatedPdfRatio - imageRatio) > ACCEPTABLE_RATIO_DIFFERENCE) {
reject('EXIF PDF width and height are invalid');
return;
}

// Swap PDF width and height if swapped ratio seems correct
const tempWidth = pdfWidth;
pdfWidth = pdfHeight;
pdfHeight = tempWidth;
}

// Resolve with valid PDF width, height, and num pages
resolve({
pdfWidth,
pdfHeight,
numPages
});
});
} catch (e) {
reject('Error reading EXIF data');
Expand Down Expand Up @@ -196,41 +274,6 @@ class DocPreloader extends EventEmitter {
};
}

/**
* Set scaled dimensions for the preload image and show.
*
* @private
* @param {number} scaledWidth - Width in pixels to scale preload to
* @param {number} scaledHeight - Height in pixels to scale preload to
* @param {number} numPages - Number of pages to show for preload
* @return {void}
*/
scaleAndShowPreload(scaledWidth, scaledHeight, numPages) {
if (this.checkDocumentLoaded()) {
return;
}

// Set image dimensions
setDimensions(this.imageEl, scaledWidth, scaledHeight);

// Add and scale correct number of placeholder elements
for (let i = 0; i < numPages - 1; i++) {
const placeholderEl = document.createElement('div');
placeholderEl.className = CLASS_BOX_PREVIEW_PRELOAD_CONTENT;
setDimensions(placeholderEl, scaledWidth, scaledHeight);
this.preloadEl.appendChild(placeholderEl);
}

// Hide the preview-level loading indicator
hideLoadingIndicator();

// Show preload element after content is properly sized
this.preloadEl.classList.remove(CLASS_INVISIBLE);

// Emit message that preload has occurred
this.emit('preload');
}

/**
* Check if full document is already loaded - if so, hide the preload.
*
Expand Down
35 changes: 3 additions & 32 deletions src/lib/viewers/doc/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import pageNumTemplate from './pageNumButtonContent.html';
import DocBase from './DocBase';
import DocPreloader from './DocPreloader';
import fullscreen from '../../Fullscreen';
import { getRepresentation } from '../../file';
import { PRELOAD_REP_NAME } from '../../constants';
import {
ICON_DROP_DOWN,
ICON_DROP_UP,
Expand All @@ -31,8 +29,8 @@ class Document extends DocBase {
this.docEl.classList.add('bp-doc-document');

// Set up preloader
this.docPreloader = new DocPreloader();
this.docPreloader.addListener('preload', () => {
this.preloader = new DocPreloader();
this.preloader.addListener('preload', () => {
this.options.logger.setPreloaded();
});
}
Expand All @@ -42,34 +40,7 @@ class Document extends DocBase {
*/
destroy() {
super.destroy();
this.docPreloader.removeAllListeners('preload');
}

/**
* @inheritdoc
*/
showPreload() {
// Don't show preload if there's a cached page since preloads are only for the 1st page
if (this.getCachedPage() !== 1) {
return;
}

const { file } = this.options;
const preloadRep = getRepresentation(file, PRELOAD_REP_NAME);
if (!preloadRep || !this.getViewerOption('preload')) {
return;
}

const { url_template: template } = preloadRep.content;
const preloadUrlWithAuth = this.createContentUrlWithAuthParams(template);
this.docPreloader.showPreload(preloadUrlWithAuth, this.containerEl);
}

/**
* @inheritdoc
*/
hidePreload() {
this.docPreloader.hidePreload();
this.preloader.removeAllListeners('preload');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we planning on adding more listeners?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for now - if we do it'll make sense to break it out into a separate function.

}

/**
Expand Down
Loading