Skip to content

Commit

Permalink
fix(viewer): Annotations render correctly for presentation files (#1114)
Browse files Browse the repository at this point in the history
  • Loading branch information
jstoffan authored and mergify[bot] committed Dec 10, 2019
1 parent 7a31aff commit 990fb00
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 8 deletions.
123 changes: 116 additions & 7 deletions src/lib/viewers/doc/PresentationViewer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import throttle from 'lodash/throttle';
import DocBaseViewer from './DocBaseViewer';
import PresentationPreloader from './PresentationPreloader';
import { CLASS_INVISIBLE } from '../../constants';
import './Presentation.scss';

const WHEEL_THROTTLE = 200;
Expand All @@ -21,6 +22,7 @@ class PresentationViewer extends DocBaseViewer {
// Bind context for callbacks
this.mobileScrollHandler = this.mobileScrollHandler.bind(this);
this.pagesinitHandler = this.pagesinitHandler.bind(this);
this.pagechangingHandler = this.pagechangingHandler.bind(this);
this.throttledWheelHandler = this.getWheelHandler().bind(this);
}

Expand Down Expand Up @@ -49,6 +51,33 @@ class PresentationViewer extends DocBaseViewer {
this.preloader.removeAllListeners('preload');
}

/**
* Go to specified page. We implement presentation mode by hiding all pages
* except for the page we are going to.
*
* @param {number} pageNum Page to navigate to
* @return {void}
*/
setPage(pageNum) {
this.checkOverflow();

// Hide all pages
const pages = this.docEl.querySelectorAll('.page');
[].forEach.call(pages, pageEl => {
pageEl.classList.add(CLASS_INVISIBLE);
});

super.setPage(pageNum);

// Show page we are navigating to
const pageEl = this.docEl.querySelector(`[data-page-number="${this.pdfViewer.currentPageNumber}"]`);
pageEl.classList.remove(CLASS_INVISIBLE);

// Force page to be rendered - this is needed because the presentation
// DOM layout can trick pdf.js into thinking that this page is not visible
this.pdfViewer.update();
}

/**
* Handles keyboard events for presentation viewer.
*
Expand Down Expand Up @@ -100,14 +129,16 @@ class PresentationViewer extends DocBaseViewer {
//--------------------------------------------------------------------------

/**
* Initialize pdf.js viewer.
* Loads PDF.js with provided PDF.
*
* @protected
* @override
* @return {pdfjsViewer.PDFViewer} PDF viewer type
* @param {string} pdfUrl The URL of the PDF to load
* @return {void}
* @protected
*/
initPdfViewer() {
return this.initPdfViewerClass(this.pdfjsViewer.PDFSinglePageViewer);
initViewer(pdfUrl) {
super.initViewer(pdfUrl);
this.overwritePdfViewerBehavior();
}

//--------------------------------------------------------------------------
Expand All @@ -125,7 +156,6 @@ class PresentationViewer extends DocBaseViewer {
super.bindDOMListeners();

this.docEl.addEventListener('wheel', this.throttledWheelHandler);

if (this.hasTouch) {
this.docEl.addEventListener('touchstart', this.mobileScrollHandler);
this.docEl.addEventListener('touchmove', this.mobileScrollHandler);
Expand All @@ -144,7 +174,6 @@ class PresentationViewer extends DocBaseViewer {
super.unbindDOMListeners();

this.docEl.removeEventListener('wheel', this.throttledWheelHandler);

if (this.hasTouch) {
this.docEl.removeEventListener('touchstart', this.mobileScrollHandler);
this.docEl.removeEventListener('touchmove', this.mobileScrollHandler);
Expand Down Expand Up @@ -185,6 +214,41 @@ class PresentationViewer extends DocBaseViewer {
}
}

/**
* Handler for 'pagesinit' event.
*
* @private
* @return {void}
*/
pagesinitHandler() {
// We implement presentation mode by hiding other pages except for the first page
const pageEls = [].slice.call(this.docEl.querySelectorAll('.pdfViewer .page'), 0);
pageEls.forEach(pageEl => {
if (pageEl.getAttribute('data-page-number') === '1') {
return;
}

pageEl.classList.add(CLASS_INVISIBLE);
});

super.pagesinitHandler();

// Initially scale the page to fit. This will change to auto on resize events.
this.pdfViewer.currentScaleValue = 'page-fit';
}

/**
* Page change handler.
*
* @private
* @param {event} e - Page change event
* @return {void}
*/
pagechangingHandler(e) {
this.setPage(e.pageNumber);
super.pagechangingHandler(e);
}

/**
* Returns throttled mousewheel handler
*
Expand All @@ -204,6 +268,51 @@ class PresentationViewer extends DocBaseViewer {
}
}, WHEEL_THROTTLE);
}

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------

/**
* Overwrite some pdf_viewer.js behavior for presentations.
*
* @private
* @return {void}
*/
overwritePdfViewerBehavior() {
// Overwrite scrollPageIntoView for presentations since we have custom pagination behavior
// This override is needed to allow PDF.js to change pages when clicking on links in a presentation that
// navigate to other pages
this.pdfViewer.scrollPageIntoView = pageObj => {
if (!this.loaded) {
return;
}

let pageNum = pageObj;
if (typeof pageNum !== 'number') {
pageNum = pageObj.pageNumber || 1;
}

this.setPage(pageNum);
};
// Overwrite _getVisiblePages for presentations to always calculate instead of fetching visible
// elements since we lay out presentations differently
this.pdfViewer._getVisiblePages = () => {
const currentPageObj = this.pdfViewer._pages[this.pdfViewer._currentPageNumber - 1];
const visible = [
{
id: currentPageObj.id,
view: currentPageObj,
},
];

return {
first: currentPageObj,
last: currentPageObj,
views: visible,
};
};
}
}

export default PresentationViewer;
155 changes: 154 additions & 1 deletion src/lib/viewers/doc/__tests__/PresentationViewer-test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/* eslint-disable no-unused-expressions */
import PresentationViewer from '../PresentationViewer';
import BaseViewer from '../../BaseViewer';
import DocBaseViewer from '../DocBaseViewer';
import PresentationPreloader from '../PresentationPreloader';
import PresentationViewer from '../PresentationViewer';
import { CLASS_INVISIBLE } from '../../../constants';

const sandbox = sinon.sandbox.create();

Expand Down Expand Up @@ -98,6 +99,58 @@ describe('lib/viewers/doc/PresentationViewer', () => {
});
});

describe('setPage()', () => {
let page1;
let page2;
let page3;

beforeEach(() => {
page1 = document.createElement('div');
page1.setAttribute('data-page-number', '1');
page1.classList.add('page');

page2 = document.createElement('div');
page2.setAttribute('data-page-number', '2');
page2.classList.add(CLASS_INVISIBLE, 'page');

page3 = document.createElement('div');
page3.setAttribute('data-page-number', '3');
page3.classList.add('page');

presentation.docEl.appendChild(page1);
presentation.docEl.appendChild(page2);
presentation.docEl.appendChild(page3);
});

afterEach(() => {
presentation.docEl.removeChild(page1);
presentation.docEl.removeChild(page2);
presentation.docEl.removeChild(page3);
});

it('should check to see if overflow is present', () => {
const checkOverflowStub = sandbox.stub(presentation, 'checkOverflow');

presentation.setPage(2);
expect(checkOverflowStub).to.be.called;
});

it('should all other pages', () => {
sandbox.stub(presentation, 'checkOverflow');
presentation.setPage(2);

expect(page1).to.have.class(CLASS_INVISIBLE);
expect(page3).to.have.class(CLASS_INVISIBLE);
});

it('should show the page being set', () => {
sandbox.stub(presentation, 'checkOverflow');
presentation.setPage(2);

expect(page2).to.not.have.class(CLASS_INVISIBLE);
});
});

describe('onKeydown()', () => {
beforeEach(() => {
stubs.previousPage = sandbox.stub(presentation, 'previousPage');
Expand Down Expand Up @@ -181,6 +234,23 @@ describe('lib/viewers/doc/PresentationViewer', () => {
});
});

describe('initViewer()', () => {
const initViewerFunc = DocBaseViewer.prototype.initViewer;

afterEach(() => {
Object.defineProperty(DocBaseViewer.prototype, 'initViewer', { value: initViewerFunc });
});

it('should overwrite the scrollPageIntoView method', () => {
const stub = sandbox.stub(presentation, 'overwritePdfViewerBehavior');
Object.defineProperty(DocBaseViewer.prototype, 'initViewer', { value: sandbox.stub() });

presentation.initViewer('url');

expect(stub).to.be.called;
});
});

describe('bindDOMListeners()', () => {
beforeEach(() => {
stubs.addEventListener = sandbox.stub(presentation.docEl, 'addEventListener');
Expand Down Expand Up @@ -297,6 +367,41 @@ describe('lib/viewers/doc/PresentationViewer', () => {
});
});

describe('pagesInitHandler()', () => {
beforeEach(() => {
stubs.setPage = sandbox.stub(presentation, 'setPage');
stubs.page1 = document.createElement('div');
stubs.page1.setAttribute('data-page-number', '1');
stubs.page1.className = 'page';

stubs.page2 = document.createElement('div');
stubs.page2.setAttribute('data-page-number', '2');
stubs.page2.className = 'page';

stubs.page3 = document.createElement('div');
stubs.page3.setAttribute('data-page-number', '3');
stubs.page3.className = 'page';

document.querySelector('.pdfViewer').appendChild(stubs.page1);
document.querySelector('.pdfViewer').appendChild(stubs.page2);
document.querySelector('.pdfViewer').appendChild(stubs.page3);
});

it('should hide all pages except for the first one', () => {
presentation.pagesinitHandler();

expect(stubs.page1).to.not.have.class(CLASS_INVISIBLE);
expect(stubs.page2).to.have.class(CLASS_INVISIBLE);
expect(stubs.page3).to.have.class(CLASS_INVISIBLE);
});

it('should set the pdf viewer scale to page-fit', () => {
presentation.pagesinitHandler();

expect(presentation.pdfViewer.currentScaleValue).to.equal('page-fit');
});
});

describe('getWheelHandler()', () => {
let wheelHandler;

Expand Down Expand Up @@ -336,4 +441,52 @@ describe('lib/viewers/doc/PresentationViewer', () => {
expect(stubs.nextPage).to.not.be.called;
});
});

describe('overwritePdfViewerBehavior()', () => {
describe('should overwrite the scrollPageIntoView method', () => {
it('should do nothing if the viewer is not loaded', () => {
const setPageStub = sandbox.stub(presentation, 'setPage');
const page = {
pageNumber: 3,
};

presentation.loaded = false;
presentation.overwritePdfViewerBehavior();
presentation.pdfViewer.scrollPageIntoView(page);

expect(setPageStub).to.not.be.called;
});

it('should change the page if the viewer is loaded', () => {
const setPageStub = sandbox.stub(presentation, 'setPage');
const page = {
pageNumber: 3,
};

presentation.loaded = true;
presentation.overwritePdfViewerBehavior();
presentation.pdfViewer.scrollPageIntoView(page);

expect(setPageStub).to.be.calledWith(3);
});
});

it('should overwrite the _getVisiblePages method', () => {
presentation.pdfViewer = {
_pages: {
0: {
id: 1,
view: 'pageObj',
},
},
_currentPageNumber: 1,
};

presentation.overwritePdfViewerBehavior();
const result = presentation.pdfViewer._getVisiblePages();

expect(result.first.id).to.equal(1);
expect(result.last.id).to.equal(1);
});
});
});

0 comments on commit 990fb00

Please sign in to comment.