From fa5dd30f00e08b5b89b957736e6131a5d6bc828d Mon Sep 17 00:00:00 2001 From: Jared Stoffan Date: Wed, 21 Apr 2021 11:03:14 -0700 Subject: [PATCH] feat(loading): Update loading ux for pages, thumbnails, preloaders (#1355) --- cypress.json | 2 +- src/lib/_loading.scss | 42 ------------------ src/lib/constants.js | 2 +- src/lib/icons/loading.gif | Bin 851 -> 0 bytes src/lib/icons/loading_ghost.gif | Bin 0 -> 3184 bytes src/lib/viewers/BaseViewer.js | 3 -- src/lib/viewers/doc/DocPreloader.js | 37 +++++++-------- src/lib/viewers/doc/Document.scss | 33 ++++++++------ src/lib/viewers/doc/Presentation.scss | 27 +++++++---- src/lib/viewers/doc/PresentationPreloader.js | 19 ++------ .../doc/__tests__/DocPreloader-test.js | 31 ++++++------- .../__tests__/PresentationPreloader-test.js | 22 +++------ src/lib/viewers/doc/_docBase.scss | 22 +++++++-- src/lib/viewers/doc/_loading.scss | 6 +++ test/support/commands.js | 4 +- 15 files changed, 106 insertions(+), 144 deletions(-) delete mode 100644 src/lib/icons/loading.gif create mode 100644 src/lib/icons/loading_ghost.gif create mode 100644 src/lib/viewers/doc/_loading.scss diff --git a/cypress.json b/cypress.json index cdc04d1a8..633c10e51 100644 --- a/cypress.json +++ b/cypress.json @@ -1,6 +1,6 @@ { "baseUrl": "http://localhost:8000/#", - "defaultCommandTimeout": 8000, + "defaultCommandTimeout": 15000, "fileServerFolder": "test", "fixturesFolder": "test/fixtures", "integrationFolder": "test/integration", diff --git a/src/lib/_loading.scss b/src/lib/_loading.scss index 0e7824821..1fc6f8d14 100644 --- a/src/lib/_loading.scss +++ b/src/lib/_loading.scss @@ -1,12 +1,5 @@ @import 'boxuiVariables'; -$spinner-size: 15px; - -@mixin spinner() { - background: url('icons/loading.gif') center no-repeat; - background-size: $spinner-size $spinner-size; -} - @keyframes box-crawler { 0%, 80%, @@ -21,17 +14,6 @@ $spinner-size: 15px; } } -@keyframes fadeIn { - 0%, - 50% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - .bp-loading-wrapper { position: absolute; top: 0; @@ -100,27 +82,3 @@ $spinner-size: 15px; animation-delay: .2s; } } - -.bp .bp-doc.bp-doc-document, -.bp .bp-doc.bp-doc-presentation { - // Overrides PDF.js loading spinner - .pdfViewer .page .loadingIcon { - @include spinner; - } -} - -.bp-document-preload-wrapper, -.bp-presentation-preload-wrapper { - .bp-preload-spinner { - @include spinner; - - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: $spinner-size; - height: $spinner-size; - margin: auto; - } -} diff --git a/src/lib/constants.js b/src/lib/constants.js index c2268906b..20602595d 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -29,7 +29,7 @@ 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_SPINNER = 'bp-preload-spinner'; +export const CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER = 'bp-preload-placeholder'; 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_NOTIFICATION = 'bp-notification'; diff --git a/src/lib/icons/loading.gif b/src/lib/icons/loading.gif deleted file mode 100644 index 3f4bf06fa5c15baf7a8cec85c4e614f16a5a5a7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 851 zcmZ?wbhEHblw*)%_{hM(VAH~ocoj$kf#QE|Ki808XU70nBRvCVMu?DSZen_>Z(@38 zayD;zVs37tt%072sfnp6gW^vXRxY3-9gt>_c??WpE&VG`zvW*%XJJ%o{;8P7eWfuo zCMKmWy8Tl2!6~+tJexoNsY|ZCx97#>qfP51d~X;@^dG&Xf8B>^n#2gqJ1A^fJ)Zz%(L(-OJVhNKg}D diff --git a/src/lib/icons/loading_ghost.gif b/src/lib/icons/loading_ghost.gif new file mode 100644 index 0000000000000000000000000000000000000000..1e10dec76bc290ed53ac2ce5871ad09151ad85e3 GIT binary patch literal 3184 zcmbtWYfzMB6#jMrxd|E-gBvA`2%Bw2P00dMxIiu|BnTqmkTBS|CJ;jm0g>S(Ka3G{ zng}DVBMP*@j^j+a5Lk}IMO_*xX-XiYX<=Ew8E=r&cHZ-T%f8>@h~>X;=A839&$)c> zIh&(jmj|cUJBYYOM{at0dTMIQXf&Ekrpd|4iHQk=!C*FJC|X?>ub zh~qflIeUj?oFgZLZ;pg>cK&MJZ~p}LM}PH=+<2+eoF*;by|-4~+Ma3b&XMW!vPKKq zXNp&^#Za*&)SV-t_Q|@7&Q^mvUPrB~|*{jfP648d|02(y>Z2f0WkeC}on0-rc?;p#UwS1Q7u_2l!gw ziHBN0{v}m>(udaCHFCy$j-ZHLdynV#WroxZDr}Mwi%BvYr8?OS(zmuX*taUtLf<;1 z`n-Z`1^a`FXUyR~>8R*k6-u8pEGibFtxuEx!Og;+u@|yp*6R}ur$cV-?HsMMxAra_ zXl4a8J0Fqe3eZSwX-)&pw18&EeQDmK*^FAU=@r>zQwG_jk!%RfqHM?yZ$z4STD0IY z{Obewzn-7xflc@;+S&B^@2s+#WU-^;|nv5L4N7*^t+NKD#rF8L4f+ zVqjTT30M~Xnx$BNwy=1fyllAHXTwqhEPt{rzkI^x^3W^~&Se}}erH)){vQkGG6EH= zg^Df8MJndF5oC#@eM!c5CUlPWrM@1YC~rsl8qbf>7eex33I9SsUz(nQpYZ4prlPZ_ zv<2P~$MovNJ(sfIXMRaFko?M!sCEcQ>L`iq(MXzr?xmfryN zYJuK|G7~lmEZ-u_R;<_e3VhNJ(d|{Fpxev)BG&7#2>wOA%~@X5=Dd(s;L*CCJz&bt z7}I*?udCJvlow@1(+!Dh?nyh2D?<(ya_9sc`zc4@LvzT1BbG@)3Gm4XuSED$0(cKT z1(`i<`P`B%?@}?^3jusJf-4ZblHHLL&UE9vOCR;}73B4WjWaGPU#L%FiYBEavL=7v zCw~U^1i;SYA`2(5{1)d|?|*>ZaD)N<5kU0_x{-it>1n`&MmzEQoNBGLX!YiVS`RRl zod>{11Pmd7arXt(uCy5rcXl{p8GSzX99M#H8wgwn9wz&~qnAcq!cto1(Qm zW9MbrWc|!Ci2$e|gpv`cb{5Kyhw8S4+QMjg8vtIm0%#Cm&k|Z#fKmW>3IU!a3QjDo zU>CD<8zK4*o?dH6^jbU`Kcz?G!8Cd_x={URY8ctw5O*Epd?9Y+0dZHDJ`O`v4@OBK zs#_3M&9nJiH}x#IYZu(x1@2Uw<_kuXqXWm4qPqv6zl8@%l>u{khY{foZXYkWrLt}w zj9VbM<)GVRfVR`>Hde`SD!@yLUasJkf?jgwP)q>(<^_AwB6|kd!_e*m@px1{&xptc nvm2W2;pSotAAw=H#h_sfWs42YndHX*91PAFVW-wrbD{qNipL`% literal 0 HcmV?d00001 diff --git a/src/lib/viewers/BaseViewer.js b/src/lib/viewers/BaseViewer.js index ea4b677d2..ce40232bd 100644 --- a/src/lib/viewers/BaseViewer.js +++ b/src/lib/viewers/BaseViewer.js @@ -88,9 +88,6 @@ class BaseViewer extends EventEmitter { /** @property {number} - Zoom scale, if zoomed */ scale = 1; - /** @property {string} - Viewer-specific file loading icon */ - fileLoadingIcon; - /** @property {Object} - Viewer options */ options; diff --git a/src/lib/viewers/doc/DocPreloader.js b/src/lib/viewers/doc/DocPreloader.js index 0fe17f333..43a726045 100644 --- a/src/lib/viewers/doc/DocPreloader.js +++ b/src/lib/viewers/doc/DocPreloader.js @@ -4,15 +4,15 @@ import { CLASS_BOX_PREVIEW_PRELOAD, CLASS_BOX_PREVIEW_PRELOAD_CONTENT, CLASS_BOX_PREVIEW_PRELOAD_OVERLAY, - CLASS_BOX_PREVIEW_PRELOAD_SPINNER, + CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER, CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_DOCUMENT, CLASS_INVISIBLE, CLASS_IS_TRANSPARENT, CLASS_PREVIEW_LOADED, PDFJS_CSS_UNITS, + PDFJS_HEIGHT_PADDING_PX, PDFJS_MAX_AUTO_SCALE, PDFJS_WIDTH_PADDING_PX, - PDFJS_HEIGHT_PADDING_PX, } from '../../constants'; import { setDimensions, handleRepresentationBlobFetch } from '../../util'; @@ -20,12 +20,10 @@ const EXIF_COMMENT_TAG_NAME = 'UserComment'; // Read EXIF data from 'UserComment const EXIF_COMMENT_REGEX = /pdfWidth:([0-9.]+)pts,pdfHeight:([0-9.]+)pts,numPages:([0-9]+)/; 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 NUM_PAGES_MAX = 20; const ACCEPTABLE_RATIO_DIFFERENCE = 0.025; // Acceptable difference in ratio of PDF dimensions to image dimensions -const SPINNER_HTML = `
`; - class DocPreloader extends EventEmitter { /** @property {Api} - Api layer used for XHR calls */ api = new Api(); @@ -39,12 +37,12 @@ class DocPreloader extends EventEmitter { /** @property {HTMLElement} - Maximum auto-zoom scale */ maxZoomScale = PDFJS_MAX_AUTO_SCALE; - /** @property {HTMLElement} - Preload overlay element */ - overlayEl; - /** @property {Object} - The EXIF data for the PDF */ pdfData; + /** @property {HTMLElement} - Preload placeholder element */ + placeholderEl; + /** @property {HTMLElement} - Preload container element */ preloadEl; @@ -101,17 +99,17 @@ class DocPreloader extends EventEmitter { this.wrapperEl.className = this.wrapperClassName; this.wrapperEl.innerHTML = `
- -
- ${SPINNER_HTML} +
+ +
`.trim(); this.containerEl.appendChild(this.wrapperEl); + this.placeholderEl = this.wrapperEl.querySelector(`.${CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER}`); 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.imageEl = this.preloadEl.querySelector(`.${CLASS_BOX_PREVIEW_PRELOAD_CONTENT}`); this.bindDOMListeners(); }); } @@ -129,22 +127,17 @@ class DocPreloader extends EventEmitter { return; } - // Set image and overlay dimensions - setDimensions(this.imageEl, scaledWidth, scaledHeight); - setDimensions(this.overlayEl, scaledWidth, scaledHeight); + // Set initial placeholder dimensions + setDimensions(this.placeholderEl, scaledWidth, scaledHeight); // Add and scale correct number of placeholder elements for (let i = 0; i < numPages - 1; i += 1) { const placeholderEl = document.createElement('div'); - placeholderEl.className = CLASS_BOX_PREVIEW_PRELOAD_CONTENT; - placeholderEl.innerHTML = SPINNER_HTML; + placeholderEl.className = CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER; setDimensions(placeholderEl, scaledWidth, scaledHeight); this.preloadEl.appendChild(placeholderEl); } - // Hide the preview-level loading indicator - this.previewUI.hideLoadingIndicator(); - // Show preload element after content is properly sized this.preloadEl.classList.remove(CLASS_INVISIBLE); @@ -292,7 +285,7 @@ class DocPreloader extends EventEmitter { const { scaledWidth, scaledHeight } = dimensionData; // Scale preload and placeholder elements - const preloadEls = this.preloadEl.getElementsByClassName(CLASS_BOX_PREVIEW_PRELOAD_CONTENT); + const preloadEls = this.preloadEl.getElementsByClassName(CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER); for (let i = 0; i < preloadEls.length; i += 1) { setDimensions(preloadEls[i], scaledWidth, scaledHeight); } diff --git a/src/lib/viewers/doc/Document.scss b/src/lib/viewers/doc/Document.scss index 780ae41d3..2c5af7923 100644 --- a/src/lib/viewers/doc/Document.scss +++ b/src/lib/viewers/doc/Document.scss @@ -34,21 +34,28 @@ } } - // Position preload content as if they were blank loading pages + .bp-preload-content, + .bp-preload-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } + .bp-preload-content { + width: 100%; + } + + .bp-preload-overlay { + background-color: rgba(255, 255, 255, .4); + } + + .bp-preload-placeholder { + @include bp-LoadingGhost; + position: relative; - display: block; margin: 15px auto 30px; - padding-left: 1px; // Slight padding to help image text align with real text - background-color: $white; - - &.bp-preload-overlay { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: rgba(255, 255, 255, .4); - } + overflow: hidden; } } diff --git a/src/lib/viewers/doc/Presentation.scss b/src/lib/viewers/doc/Presentation.scss index 30f030477..be5e9c412 100644 --- a/src/lib/viewers/doc/Presentation.scss +++ b/src/lib/viewers/doc/Presentation.scss @@ -53,19 +53,28 @@ } } - // Absolutely center preload content - .bp-preload-content { + .bp-preload-content, + .bp-preload-overlay, + .bp-preload-placeholder { position: absolute; top: 0; right: 0; bottom: 0; - left: 1px; // Slight padding to help image text align with real text - display: block; - margin: auto; - background-color: $white; + left: 0; + } - &.bp-preload-overlay { - background-color: rgba(255, 255, 255, .4); - } + .bp-preload-content { + width: 100%; + } + + .bp-preload-overlay { + background-color: rgba(255, 255, 255, .4); + } + + .bp-preload-placeholder { + @include bp-LoadingGhost; + + margin: auto; + overflow: hidden; } } diff --git a/src/lib/viewers/doc/PresentationPreloader.js b/src/lib/viewers/doc/PresentationPreloader.js index 00290a21c..b43f1b10e 100644 --- a/src/lib/viewers/doc/PresentationPreloader.js +++ b/src/lib/viewers/doc/PresentationPreloader.js @@ -3,18 +3,10 @@ import { CLASS_INVISIBLE, CLASS_BOX_PREVIEW_PRELOAD_WRAPPER_PRESENTATION } from import { setDimensions } from '../../util'; class PresentationPreloader extends DocPreloader { - /** - * @property {HTMLELement} - Maximum auto-zoom scale, set to 0 for no limit since presentation viewer doesn't - * have a maximum zoom scale and scales up to available viewport - */ + /** @inheritdoc */ maxZoomScale = 0; - /** - * [constructor] - * - * @param {PreviewUI} previewUI - UI instance - * @return {PresentationPreloader} PresentationPreloader instance - */ + /** @inheritdoc */ constructor(previewUI, options) { super(previewUI, options); @@ -35,11 +27,8 @@ class PresentationPreloader extends DocPreloader { return; } - setDimensions(this.imageEl, scaledWidth, scaledHeight); - setDimensions(this.overlayEl, scaledWidth, scaledHeight); - - // Hide the preview-level loading indicator - this.previewUI.hideLoadingIndicator(); + // Set initial placeholder dimensions + setDimensions(this.placeholderEl, scaledWidth, scaledHeight); // Show preload element after content is properly sized this.preloadEl.classList.remove(CLASS_INVISIBLE); diff --git a/src/lib/viewers/doc/__tests__/DocPreloader-test.js b/src/lib/viewers/doc/__tests__/DocPreloader-test.js index d8398000f..0e5892d2a 100644 --- a/src/lib/viewers/doc/__tests__/DocPreloader-test.js +++ b/src/lib/viewers/doc/__tests__/DocPreloader-test.js @@ -1,11 +1,12 @@ /* eslint-disable no-unused-expressions */ +import * as util from '../../../util'; import Api from '../../../api'; import DocPreloader from '../DocPreloader'; -import * as util from '../../../util'; import { CLASS_BOX_PREVIEW_PRELOAD, CLASS_BOX_PREVIEW_PRELOAD_CONTENT, CLASS_BOX_PREVIEW_PRELOAD_OVERLAY, + CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER, CLASS_INVISIBLE, CLASS_PREVIEW_LOADED, } from '../../../constants'; @@ -22,10 +23,9 @@ describe('lib/viewers/doc/DocPreloader', () => { containerEl = document.querySelector('.container'); stubs = {}; stubs.api = new Api(); - docPreloader = new DocPreloader({ hideLoadingIndicator: () => {} }, { api: stubs.api }); + docPreloader = new DocPreloader({}, { api: stubs.api }); docPreloader.previewUI = { - hideLoadingIndicator: jest.fn(), previewContainer: document.createElement('div'), }; }); @@ -56,6 +56,7 @@ describe('lib/viewers/doc/DocPreloader', () => { expect(docPreloader.wrapperEl).toContainSelector(`.${CLASS_BOX_PREVIEW_PRELOAD}`); expect(docPreloader.preloadEl).toContainSelector(`.${CLASS_BOX_PREVIEW_PRELOAD_CONTENT}`); expect(docPreloader.preloadEl).toContainSelector(`.${CLASS_BOX_PREVIEW_PRELOAD_OVERLAY}`); + expect(docPreloader.preloadEl).toContainSelector(`.${CLASS_BOX_PREVIEW_PRELOAD_PLACEHOLDER}`); expect(docPreloader.imageEl.src).toBe(imgSrc); expect(containerEl).toContainElement(docPreloader.wrapperEl); expect(docPreloader.bindDOMListeners).toBeCalled(); @@ -68,7 +69,6 @@ describe('lib/viewers/doc/DocPreloader', () => { stubs.checkDocumentLoaded = jest.spyOn(docPreloader, 'checkDocumentLoaded').mockImplementation(); stubs.emit = jest.spyOn(docPreloader, 'emit').mockImplementation(); stubs.setDimensions = jest.spyOn(util, 'setDimensions').mockImplementation(); - stubs.hideLoadingIndicator = docPreloader.previewUI.hideLoadingIndicator; docPreloader.imageEl = {}; docPreloader.preloadEl = document.createElement('div'); }); @@ -79,10 +79,9 @@ describe('lib/viewers/doc/DocPreloader', () => { docPreloader.scaleAndShowPreload(1, 1, 1); expect(stubs.setDimensions).not.toBeCalled(); - expect(stubs.hideLoadingIndicator).not.toBeCalled(); }); - test('should set preload image dimensions, hide loading indicator, show preload element, and emit preload event', () => { + test('should set preload image dimensions, show preload element, and emit preload event', () => { docPreloader.preloadEl.classList.add(CLASS_INVISIBLE); const width = 100; @@ -90,9 +89,7 @@ describe('lib/viewers/doc/DocPreloader', () => { docPreloader.scaleAndShowPreload(width, height, 1); - expect(stubs.setDimensions).toBeCalledWith(docPreloader.imageEl, width, height); - expect(stubs.setDimensions).toBeCalledWith(docPreloader.overlayEl, width, height); - expect(stubs.hideLoadingIndicator).toBeCalled(); + expect(stubs.setDimensions).toBeCalledWith(docPreloader.placeholderEl, width, height); expect(stubs.emit).toBeCalledWith('preload'); expect(docPreloader.preloadEl).not.toHaveClass(CLASS_INVISIBLE); }); @@ -102,7 +99,7 @@ describe('lib/viewers/doc/DocPreloader', () => { docPreloader.scaleAndShowPreload(100, 100, numPages); // Should scale 1 preload image, one overlay, and numPages - 1 placeholders - expect(stubs.setDimensions).toBeCalledTimes(numPages + 1); + expect(stubs.setDimensions).toBeCalledTimes(numPages); expect(docPreloader.preloadEl.children.length).toBe(numPages - 1); }); @@ -280,12 +277,11 @@ describe('lib/viewers/doc/DocPreloader', () => { }); test('should only show up to NUM_PAGES_MAX pages', () => { - const NUM_PAGES_MAX = 500; stubs.readEXIF.mockReturnValue( Promise.resolve({ pdfWidth: 100, pdfHeight: 100, - numPages: NUM_PAGES_MAX + 1, // NUM_PAGES_MAX + 1 + numPages: 21, // NUM_PAGES_MAX + 1 }), ); @@ -297,7 +293,7 @@ describe('lib/viewers/doc/DocPreloader', () => { docPreloader.preloadEl = {}; docPreloader.imageEl = {}; return docPreloader.loadHandler().then(() => { - expect(stubs.scaleAndShowPreload).toBeCalledWith(200, 200, NUM_PAGES_MAX); + expect(stubs.scaleAndShowPreload).toBeCalledWith(200, 200, 20); }); }); @@ -567,7 +563,12 @@ describe('lib/viewers/doc/DocPreloader', () => { }); jest.spyOn(util, 'setDimensions').mockImplementation(); docPreloader.preloadEl = document.createElement('div'); - docPreloader.preloadEl.innerHTML = `
`; + docPreloader.preloadEl.innerHTML = ` +
+ +
+
+ `.trim(); }); test('should short circuit if there is no preload element to resize', () => { @@ -613,7 +614,7 @@ describe('lib/viewers/doc/DocPreloader', () => { }; docPreloader.resize(); expect(docPreloader.getScaledWidthAndHeight).toBeCalled(); - expect(util.setDimensions).toBeCalledTimes(2); + expect(util.setDimensions).toBeCalledTimes(1); }); }); diff --git a/src/lib/viewers/doc/__tests__/PresentationPreloader-test.js b/src/lib/viewers/doc/__tests__/PresentationPreloader-test.js index 0bfc1cc08..a37df1ec5 100644 --- a/src/lib/viewers/doc/__tests__/PresentationPreloader-test.js +++ b/src/lib/viewers/doc/__tests__/PresentationPreloader-test.js @@ -1,6 +1,6 @@ /* eslint-disable no-unused-expressions */ -import PresentationPreloader from '../PresentationPreloader'; import * as util from '../../../util'; +import PresentationPreloader from '../PresentationPreloader'; import { CLASS_INVISIBLE } from '../../../constants'; let preloader; @@ -9,15 +9,8 @@ let stubs; describe('lib/viewers/doc/PresentationPreloader', () => { beforeEach(() => { fixture.load('viewers/doc/__tests__/PresentationPreloader-test.html'); - preloader = new PresentationPreloader({ - hideLoadingIndicator: () => {}, - }); - preloader.previewUI = { - hideLoadingIndicator: jest.fn(), - }; - stubs = { - hideLoadingIndicator: preloader.previewUI.hideLoadingIndicator, - }; + preloader = new PresentationPreloader({}); + stubs = {}; }); afterEach(() => { @@ -29,7 +22,7 @@ describe('lib/viewers/doc/PresentationPreloader', () => { stubs.checkDocumentLoaded = jest.spyOn(preloader, 'checkDocumentLoaded').mockImplementation(); stubs.emit = jest.spyOn(preloader, 'emit').mockImplementation(); stubs.setDimensions = jest.spyOn(util, 'setDimensions').mockImplementation(); - preloader.imageEl = {}; + preloader.placeholderEl = document.createElement('div'); preloader.preloadEl = document.createElement('div'); }); @@ -39,10 +32,9 @@ describe('lib/viewers/doc/PresentationPreloader', () => { preloader.scaleAndShowPreload(1, 1, 1); expect(stubs.setDimensions).not.toBeCalled(); - expect(stubs.hideLoadingIndicator).not.toBeCalled(); }); - test('should set preload image dimensions, hide loading indicator, show preload element, and emit preload event', () => { + test('should set preload image dimensions, show preload element, and emit preload event', () => { preloader.preloadEl.classList.add(CLASS_INVISIBLE); const width = 100; @@ -50,9 +42,7 @@ describe('lib/viewers/doc/PresentationPreloader', () => { preloader.scaleAndShowPreload(width, height, 1); - expect(stubs.setDimensions).toBeCalledWith(preloader.imageEl, width, height); - expect(stubs.setDimensions).toBeCalledWith(preloader.overlayEl, width, height); - expect(stubs.hideLoadingIndicator).toBeCalled(); + expect(stubs.setDimensions).toBeCalledWith(preloader.placeholderEl, width, height); expect(stubs.emit).toBeCalledWith('preload'); expect(preloader.preloadEl).not.toHaveClass(CLASS_INVISIBLE); }); diff --git a/src/lib/viewers/doc/_docBase.scss b/src/lib/viewers/doc/_docBase.scss index 7749237d2..793031e09 100644 --- a/src/lib/viewers/doc/_docBase.scss +++ b/src/lib/viewers/doc/_docBase.scss @@ -1,9 +1,11 @@ @import '../../boxuiVariables'; @import './annotations'; +@import './loading'; @import './theme'; $pdfjs-highlight: #b400aa !default; $pdfjs-highlight-selected: #006400 !default; +$pdfjs-page-padding: 15px; $thumbnail-border-radius: 6px; $thumbnail-sidebar-width: 191px; // Extra pixel to account for sidebar border @@ -70,6 +72,12 @@ $thumbnail-sidebar-width: 191px; // Extra pixel to account for sidebar border } } + .bp-thumbnail:not(.bp-thumbnail-image-loaded) { + .bp-thumbnail-nav { + @include bp-LoadingGhost; + } + } + .bp-thumbnail-nav { width: 150px; overflow: hidden; @@ -243,15 +251,21 @@ $thumbnail-sidebar-width: 191px; // Extra pixel to account for sidebar border margin: 0 auto; // We use padding instead of margin to push down since we want to // include this padding when PDF.js jumps to pages - padding: 15px 0; + padding: $pdfjs-page-padding 0; overflow: visible; border: 0; border-image: none; - // Override loading icon styles from pdf.js - we use a CSS spinner absolutely centered + // Override loading icon styles from pdf.js .loadingIcon { - margin: auto; + top: $pdfjs-page-padding; + bottom: $pdfjs-page-padding; background: none; + + // Display the ghost state only if the page does not already have rendered content (e.g. during a resize) + &:first-child { + @include bp-LoadingGhost; + } } // Fixes annotation icon broken src @@ -270,7 +284,7 @@ $thumbnail-sidebar-width: 191px; // Extra pixel to account for sidebar border } .textLayer { - top: 15px; // Match 15px padding top on page + top: $pdfjs-page-padding; bottom: auto; opacity: 1; diff --git a/src/lib/viewers/doc/_loading.scss b/src/lib/viewers/doc/_loading.scss new file mode 100644 index 000000000..f5244d08d --- /dev/null +++ b/src/lib/viewers/doc/_loading.scss @@ -0,0 +1,6 @@ +@import '~box-ui-elements/es/styles/constants/colors'; + +@mixin bp-LoadingGhost { + background: $bdl-gray-05 url('../../icons/loading_ghost.gif') repeat-y; + background-size: 100% 100%; +} diff --git a/test/support/commands.js b/test/support/commands.js index 5d2e45960..6cc875985 100644 --- a/test/support/commands.js +++ b/test/support/commands.js @@ -3,9 +3,7 @@ Cypress.Commands.add('getByTitle', (title, options = {}) => cy.get(`[title="${ti Cypress.Commands.add('getPreviewPage', pageNum => { cy.get(`.page[data-page-number=${pageNum}]`) .as('previewPage') - // Adding timeout here because sometimes it takes more than the Cypress - // default timeout to render the preview - .find('.loadingIcon', { timeout: 15000 }) + .find('.loadingIcon') .should('not.exist'); return cy.get('@previewPage');