Skip to content

Commit

Permalink
Update: Add Instant Preview loading overlay (#350)
Browse files Browse the repository at this point in the history
- Add transparent overlay over document Instant Preview to better indicate that file is still loading
- Replace document loading icon GIF with CSS
  • Loading branch information
tonyjin authored Aug 30, 2017
1 parent 77a38a0 commit c361317
Show file tree
Hide file tree
Showing 13 changed files with 739 additions and 435 deletions.
48 changes: 47 additions & 1 deletion src/lib/_loading.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
font-size: 12px;
}

// This crawler wrapper is used duing preview loading
// This crawler wrapper is used during preview loading
.bp-crawler-wrapper {
position: absolute;
top: 72px;
Expand Down Expand Up @@ -99,3 +99,49 @@
margin-left: 2px;
}
}

@keyframes bp-spinner {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}

100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}

// Center spinner horizontally and vertically within container
.bp-spinner {
bottom: 0;
left: 0;
margin: auto;
position: absolute;
right: 0;
top: 0;
}

.bp-spinner div,
.bp-spinner div::after {
border: 25px solid #0061d5;
border-radius: 50%;
border-top-color: transparent;
height: 170px;
position: absolute;
width: 170px;
}

.bp-spinner div {
animation: bp-spinner 1s linear infinite;
left: 100px;
top: 100px;
}

.bp-spinner div::after {
transform: rotate(90deg);
}

.bp-spinner {
height: 30px;
transform: translate(-15px, -15px) scale(.15) translate(15px, 15px);
width: 30px;
}
4 changes: 4 additions & 0 deletions src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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_OVERLAY = 'bp-preload-overlay';
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_PROGRESS_BAR = 'bp-progress-bar';
Expand All @@ -31,11 +32,14 @@ export const CLASS_ELEM_KEYBOARD_FOCUS = 'bp-has-keyboard-focus';
export const CLASS_FULLSCREEN = 'bp-is-fullscreen';
export const CLASS_FULLSCREEN_UNSUPPORTED = 'bp-fullscreen-unsupported';
export const CLASS_INVISIBLE = 'bp-is-invisible';
export const CLASS_IS_TRANSPARENT = 'bp-is-transparent';
export const CLASS_IS_VISIBLE = 'bp-is-visible';
export const CLASS_IS_SCROLLABLE = 'bp-is-scrollable';
export const CLASS_IS_SELECTABLE = 'bp-is-selectable';
export const CLASS_IS_BUFFERING = 'bp-is-buffering';
export const CLASS_DARK = 'bp-dark';
export const CLASS_CRAWLER = 'bp-crawler';
export const CLASS_SPINNER = 'bp-spinner';

export const SELECTOR_BOX_PREVIEW_CONTAINER = `.${CLASS_BOX_PREVIEW_CONTAINER}`;
export const SELECTOR_BOX_PREVIEW = `.${CLASS_BOX_PREVIEW}`;
Expand Down
13 changes: 12 additions & 1 deletion src/lib/viewers/doc/DocBaseViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import Popup from '../../Popup';
import RepStatus from '../../RepStatus';
import {
CLASS_BOX_PREVIEW_FIND_BAR,
CLASS_CRAWLER,
CLASS_HIDDEN,
CLASS_IS_SCROLLABLE,
CLASS_SPINNER,
DOC_STATIC_ASSETS_VERSION,
PERMISSION_DOWNLOAD,
PRELOAD_REP_NAME,
Expand Down Expand Up @@ -577,6 +579,15 @@ class DocBaseViewer extends BaseViewer {
// Do not disable create object URL in IE11 or iOS Chrome - pdf.js issues #3977 and #8081 are
// not applicable to Box's use case and disabling causes performance issues
PDFJS.disableCreateObjectURL = false;

// Customize pdf.js loading icon. We modify the prototype of PDFPageView to get around directly modifying
// pdf_viewer.js
const resetFunc = PDFJS.PDFPageView.prototype.reset;
PDFJS.PDFPageView.prototype.reset = function reset(...args) {
resetFunc.bind(this)(args);
this.loadingIconDiv.classList.add(CLASS_SPINNER);
this.loadingIconDiv.innerHTML = '<div></div>';
};
}

/**
Expand All @@ -593,7 +604,7 @@ class DocBaseViewer extends BaseViewer {
printCheckmark.innerHTML = ICON_PRINT_CHECKMARK.trim();

const loadingIndicator = document.createElement('div');
loadingIndicator.classList.add('bp-crawler');
loadingIndicator.classList.add(CLASS_CRAWLER);
loadingIndicator.innerHTML = `
<div></div>
<div></div>
Expand Down
66 changes: 60 additions & 6 deletions src/lib/viewers/doc/DocPreloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import EventEmitter from 'events';
import {
CLASS_BOX_PREVIEW_PRELOAD,
CLASS_BOX_PREVIEW_PRELOAD_CONTENT,
CLASS_BOX_PREVIEW_PRELOAD_OVERLAY,
CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_DOCUMENT,
CLASS_INVISIBLE,
CLASS_PREVIEW_LOADED
CLASS_IS_TRANSPARENT,
CLASS_PREVIEW_LOADED,
CLASS_SPINNER
} from '../../constants';
import { get, setDimensions } from '../../util';

Expand All @@ -21,7 +24,33 @@ 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

const SPINNER_HTML = `<div class="${CLASS_SPINNER}"><div></div></div>`;

class DocPreloader extends EventEmitter {
/** @property {HTMLElement} - Viewer container */
containerEl;

/** @property {HTMLElement} - Preload image element */
imageEl;

/** @property {HTMLElement} - Preload overlay element */
overlayEl;

/** @property {HTMLElement} - Preload container element */
preloadEl;

/** @property {PreviewUI} - Preview's UI instance */
previewUI;

/** @property {string} - Preload representation content URL */
srcUrl;

/** @property {string} - Class name for preload wrapper */
wrapperClassName;

/** @property {HTMLElement} - Preload wrapper element */
wrapperEl;

/**
* [constructor]
*
Expand Down Expand Up @@ -59,12 +88,16 @@ class DocPreloader extends EventEmitter {
this.wrapperEl.innerHTML = `
<div class="${CLASS_BOX_PREVIEW_PRELOAD} ${CLASS_INVISIBLE}">
<img class="${CLASS_BOX_PREVIEW_PRELOAD_CONTENT}" src="${this.srcUrl}" />
<div class="${CLASS_BOX_PREVIEW_PRELOAD_CONTENT} ${CLASS_BOX_PREVIEW_PRELOAD_OVERLAY}">
${SPINNER_HTML}
</div>
</div>
`.trim();

this.containerEl.appendChild(this.wrapperEl);
this.preloadEl = this.wrapperEl.querySelector(`.${CLASS_BOX_PREVIEW_PRELOAD}`);
this.imageEl = this.preloadEl.querySelector(`img.${CLASS_BOX_PREVIEW_PRELOAD_CONTENT}`);
this.overlayEl = this.preloadEl.querySelector(`.${CLASS_BOX_PREVIEW_PRELOAD_OVERLAY}`);
this.bindDOMListeners();
});
}
Expand All @@ -82,13 +115,15 @@ class DocPreloader extends EventEmitter {
return;
}

// Set image dimensions
// Set image and overlay dimensions
setDimensions(this.imageEl, scaledWidth, scaledHeight);
setDimensions(this.overlayEl, 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;
placeholderEl.innerHTML = SPINNER_HTML;
setDimensions(placeholderEl, scaledWidth, scaledHeight);
this.preloadEl.appendChild(placeholderEl);
}
Expand All @@ -113,18 +148,37 @@ class DocPreloader extends EventEmitter {
return;
}

this.restoreScrollPosition();
this.unbindDOMListeners();
this.restoreScrollPosition();
this.wrapperEl.classList.add(CLASS_IS_TRANSPARENT);

// Cleanup preload DOM after fade out
this.wrapperEl.addEventListener('transitionend', this.cleanupPreload);

// Cleanup preload DOM immediately if user scrolls after the document is ready since we don't want half-faded
// out preload content to be on top of real document content while scrolling
this.wrapperEl.addEventListener('scroll', this.cleanupPreload);
}

/**
* Cleans up preload DOM.
*
* @private
* @return {void}
*/
cleanupPreload = () => {
if (this.wrapperEl) {
this.wrapperEl.parentNode.removeChild(this.wrapperEl);
this.wrapperEl = undefined;
}

this.wrapperEl.parentNode.removeChild(this.wrapperEl);
this.wrapperEl = undefined;
this.preloadEl = undefined;
this.imageEl = undefined;

if (this.srcUrl) {
URL.revokeObjectURL(this.srcUrl);
}
}
};

/**
* Binds event listeners for preload
Expand Down
21 changes: 19 additions & 2 deletions src/lib/viewers/doc/Document.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@
bottom: 0;
left: 0;
margin: 0;
opacity: 1;
overflow-y: scroll;
position: absolute;
right: 0;
top: 0;
transition: opacity .5s;

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

&.bp-is-transparent {
opacity: 0;
}

.bp-preload {
Expand All @@ -30,8 +36,19 @@

// Position preload content as if they were blank loading pages
.bp-preload-content {
background: $white url('loadingIcon.gif') center no-repeat;
background-color: $white;
display: block;
margin: 15px auto 30px;
padding-left: 1px; // Slight padding to help image text align with real text
position: relative;

&.bp-preload-overlay {
background-color: rgba(255, 255, 255, .4);
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
}
}
15 changes: 12 additions & 3 deletions src/lib/viewers/doc/Presentation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@
position: absolute;
right: 0;
top: 0;
transition: opacity .5s;

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

&.bp-is-transparent {
opacity: 0;
}

.bp-preload {
Expand All @@ -50,13 +55,17 @@

// Absolutely center preload content
.bp-preload-content {
background: $white url('loadingIcon.gif') center no-repeat;
background-color: $white;
bottom: 0;
display: block;
left: 0;
left: 1px; // Slight padding to help image text align with real text
margin: auto;
position: absolute;
right: 0;
top: 0;

&.bp-preload-overlay {
background-color: rgba(255, 255, 255, .4);
}
}
}
10 changes: 10 additions & 0 deletions src/lib/viewers/doc/PresentationPreloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { CLASS_INVISIBLE, CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_PRESENTATION } from
import { setDimensions } from '../../util';

class PresentationPreloader extends DocPreloader {
/** @property {HTMLElement} - Preload container element */
preloadEl;

/** @property {PreviewUI} - Preview's UI instance */
previewUI;

/** @property {string} - Class name for preload wrapper */
wrapperClassName;

/**
* [constructor]
*
Expand All @@ -29,6 +38,7 @@ class PresentationPreloader extends DocPreloader {
}

setDimensions(this.imageEl, scaledWidth, scaledHeight);
setDimensions(this.overlayEl, scaledWidth, scaledHeight);

// Hide the preview-level loading indicator
this.previewUI.hideLoadingIndicator();
Expand Down
6 changes: 6 additions & 0 deletions src/lib/viewers/doc/__tests__/DocBaseViewer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,12 @@ describe('src/lib/viewers/doc/DocBaseViewer', () => {
docBase.setupPdfjs();
expect(PDFJS.disableCreateObjectURL).to.equal(false);
});

it('should override pdf.js PDFPageView reset with custom loading indicator logic', () => {
const resetFunc = PDFJS.PDFPageView.prototype.reset;
docBase.setupPdfjs();
expect(resetFunc).to.not.equal(PDFJS.PDFPageView.prototype.reset);
});
});

describe('initPrint()', () => {
Expand Down
Loading

0 comments on commit c361317

Please sign in to comment.