Commit c1abf185 authored by Robert Knight's avatar Robert Knight

Preserve scroll position when toggling sidebar in VitalSource PDFs

Use the `preserveScrollPosition` utility that was created for HTML / EPUB
side-by-side mode to preserve scroll position when side-by-side mode is toggled
in VitalSource PDFs.

 - Add a way to synchronously update the hidden text layer in VS PDFs

 - Wrap image size updates in VS PDFs in `preserveScrollPosition`
parent bfe0e9a7
...@@ -194,6 +194,20 @@ export class ImageTextLayer { ...@@ -194,6 +194,20 @@ export class ImageTextLayer {
this._listeners.add(window, 'resize', this._updateBoxSizes); this._listeners.add(window, 'resize', this._updateBoxSizes);
} }
/**
* Synchronously update the text layer to match the size and position of
* the image.
*
* Normally the text layer is resized automatically but asynchronously when
* the image size changes (eg. due to the window being resized) and updates
* are debounced. This method can be used to force an immediate update if
* needed.
*/
updateSync() {
this._updateBoxSizes();
this._updateBoxSizes.flush();
}
destroy() { destroy() {
this.container.remove(); this.container.remove();
this._listeners.removeAll(); this._listeners.removeAll();
......
...@@ -277,6 +277,28 @@ describe('ImageTextLayer', () => { ...@@ -277,6 +277,28 @@ describe('ImageTextLayer', () => {
assert.calledOnce(measureImageSpy); assert.calledOnce(measureImageSpy);
}); });
describe('#updateSync', () => {
it('flushes text layer size updates immediately', () => {
const { image } = createPageImage();
const imageText = 'some text in the image';
const textLayer = createTextLayer(
image,
createCharBoxes(imageText),
imageText
);
// Spy on logic that is invoked each time a resize event is handled.
const measureImageSpy = sinon.spy(image, 'getBoundingClientRect');
image.style.width = '250px';
image.style.height = '250px';
textLayer.updateSync();
assert.calledOnce(measureImageSpy);
});
});
describe('#destroy', () => { describe('#destroy', () => {
it('removes the <hypothesis-text-layer> element', () => { it('removes the <hypothesis-text-layer> element', () => {
const { container, image } = createPageImage(); const { container, image } = createPageImage();
......
...@@ -299,6 +299,7 @@ describe('annotator/integrations/vitalsource', () => { ...@@ -299,6 +299,7 @@ describe('annotator/integrations/vitalsource', () => {
fakeImageTextLayer = { fakeImageTextLayer = {
container: document.createElement('div'), container: document.createElement('div'),
destroy: sinon.stub(), destroy: sinon.stub(),
updateSync: sinon.stub(),
}; };
FakeImageTextLayer = sinon.stub().returns(fakeImageTextLayer); FakeImageTextLayer = sinon.stub().returns(fakeImageTextLayer);
...@@ -414,11 +415,13 @@ describe('annotator/integrations/vitalsource', () => { ...@@ -414,11 +415,13 @@ describe('annotator/integrations/vitalsource', () => {
integration.fitSideBySide({ expanded: true, width: sidebarWidth }); integration.fitSideBySide({ expanded: true, width: sidebarWidth });
assert.equal(fakePageImage.parentElement.style.textAlign, 'left'); assert.equal(fakePageImage.parentElement.style.textAlign, 'left');
assert.equal(fakePageImage.style.width, `${expectedWidth}px`); assert.equal(fakePageImage.style.width, `${expectedWidth}px`);
assert.calledOnce(fakeImageTextLayer.updateSync);
// Deactivate side-by-side mode. Style overrides should be removed. // Deactivate side-by-side mode. Style overrides should be removed.
integration.fitSideBySide({ expanded: false }); integration.fitSideBySide({ expanded: false });
assert.equal(fakePageImage.parentElement.style.textAlign, ''); assert.equal(fakePageImage.parentElement.style.textAlign, '');
assert.equal(fakePageImage.style.width, ''); assert.equal(fakePageImage.style.width, '');
assert.calledTwice(fakeImageTextLayer.updateSync);
}); });
it('does not resize page image if there is not enough space', () => { it('does not resize page image if there is not enough space', () => {
......
import { ListenerCollection } from '../../shared/listener-collection'; import { ListenerCollection } from '../../shared/listener-collection';
import { HTMLIntegration } from './html'; import { HTMLIntegration } from './html';
import { preserveScrollPosition } from './html-side-by-side';
import { ImageTextLayer } from './image-text-layer'; import { ImageTextLayer } from './image-text-layer';
import { injectClient } from '../hypothesis-injector'; import { injectClient } from '../hypothesis-injector';
...@@ -292,22 +293,31 @@ export class VitalSourceContentIntegration { ...@@ -292,22 +293,31 @@ export class VitalSourceContentIntegration {
const bookContainer = /** @type {HTMLElement} */ ( const bookContainer = /** @type {HTMLElement} */ (
bookImage.parentElement bookImage.parentElement
); );
const textLayer = this._textLayer;
// Update the PDF image size and alignment to fit alongside the sidebar. // Update the PDF image size and alignment to fit alongside the sidebar.
// `ImageTextLayer` will handle adjusting the text layer to match. // `ImageTextLayer` will handle adjusting the text layer to match.
const newWidth = window.innerWidth - layout.width; const newWidth = window.innerWidth - layout.width;
const minWidth = 250; const minWidth = 250;
if (layout.expanded && newWidth > minWidth) { preserveScrollPosition(() => {
// The VS book viewer sets `text-align: center` on the <body> element if (layout.expanded && newWidth > minWidth) {
// by default, which centers the book image in the page. When the sidebar // The VS book viewer sets `text-align: center` on the <body> element
// is open we need the image to be left-aligned. // by default, which centers the book image in the page. When the sidebar
bookContainer.style.textAlign = 'left'; // is open we need the image to be left-aligned.
bookImage.style.width = `${newWidth}px`; bookContainer.style.textAlign = 'left';
} else { bookImage.style.width = `${newWidth}px`;
bookContainer.style.textAlign = ''; } else {
bookImage.style.width = ''; bookContainer.style.textAlign = '';
} bookImage.style.width = '';
}
// Update text layer to match new image dimensions immediately. This
// is needed so that `preserveScrollPosition` can see how the content
// has shifted when this callback returns.
textLayer.updateSync();
});
return layout.expanded; return layout.expanded;
} else { } else {
return this._htmlIntegration.fitSideBySide(layout); return this._htmlIntegration.fitSideBySide(layout);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment