Commit 7ba89df5 authored by Robert Knight's avatar Robert Knight

Share `<mosaic-book>` custom element between EPUB and PDF books

Refactor MosaicBookElement so that it can be used for both the EPUB and
PDF book test cases.
parent a02defc8
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
Gutenberg EPUB</a>. Gutenberg EPUB</a>.
</p> </p>
<mosaic-book></mosaic-book> <mosaic-book book="little-women"></mosaic-book>
{{{hypothesisScript}}} {{{hypothesisScript}}}
</body> </body>
......
...@@ -10,55 +10,8 @@ ...@@ -10,55 +10,8 @@
} }
</style> </style>
<script type="module"> <script type="module">
/** import { MosaicBookElement } from '/scripts/vitalsource-mosaic-book-element.js';
* Custom element that holds the book content frame. The class name is not customElements.define('mosaic-book', MosaicBookElement);
* important but the `<mosaic-book>` tag name triggers the VitalSource integration.
*/
class MosaicElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// For now there is only one page.
const pageURLs = [
'/document/vitalsource-pdf-page'
];
let pageIndex = 0;
const setPage = index => {
if (index < 0 || index >= pageURLs.length) {
return;
}
pageIndex = index;
// We remove the current frame and create a new one, rather than just
// change the `src` of the existing iframe, to mimic what Bookshelf
// does. The client should be robust to either approach.
this.contentFrame?.remove();
this.contentFrame = document.createElement('iframe');
this.contentFrame.src = pageURLs[pageIndex];
this.shadowRoot.append(this.contentFrame);
};
const styles = document.createElement('style');
styles.innerHTML = `
iframe {
width: 100%;
height: 800px;
resize: both;
overflow: auto;
}
`;
this.shadowRoot.append(styles);
const controlBar = document.createElement('div');
this.shadowRoot.append(controlBar);
setPage(0);
}
}
customElements.define('mosaic-book', MosaicElement);
// The `window.VST` object must exist in the parent frame, otherwise the // The `window.VST` object must exist in the parent frame, otherwise the
// `window.innerPageData` variable is not set in the content frame. // `window.innerPageData` variable is not set in the content frame.
...@@ -84,7 +37,7 @@ ...@@ -84,7 +37,7 @@
by searching for the book ID (9781938168239) in the Bookshelf store. by searching for the book ID (9781938168239) in the Bookshelf store.
</p> </p>
<mosaic-book></mosaic-book> <mosaic-book book="test-pdf"></mosaic-book>
{{{hypothesisScript}}} {{{hypothesisScript}}}
</body> </body>
......
...@@ -10,59 +10,28 @@ export class MosaicBookElement extends HTMLElement { ...@@ -10,59 +10,28 @@ export class MosaicBookElement extends HTMLElement {
super(); super();
this.attachShadow({ mode: 'open' }); this.attachShadow({ mode: 'open' });
const chapterURLs = [ const book = this.getAttribute('book');
'/document/little-women-1',
'/document/little-women-2',
'/document/little-women-3',
];
let chapterIndex = 0; if (book === 'little-women') {
this.chapterURLs = [
'/document/little-women-1',
'/document/little-women-2',
'/document/little-women-3',
];
} else if (book === 'test-pdf') {
this.chapterURLs = ['/document/vitalsource-pdf-page'];
} else {
console.warn(`Unknown VitalSource book "${book}"`);
this.chapterURLs = [];
}
const setChapter = (index, { initialLoad = false } = {}) => { this.chapterIndex = 0;
if (index < 0 || index >= chapterURLs.length) {
return;
}
chapterIndex = index;
// We remove the current frame and create a new one, rather than just
// change the `src` of the existing iframe, to mimic what Bookshelf
// does. The client should be robust to either approach.
this.contentFrame?.remove();
this.contentFrame = document.createElement('iframe');
this.shadowRoot.append(this.contentFrame);
const chapterURL = chapterURLs[chapterIndex];
if (initialLoad) {
// Simulate client loading after VS chapter content has already
// loaded.
this.contentFrame.src = chapterURL;
} else {
// Simulate chapter navigation after client is injected. These
// navigations happen in several stages:
//
// 1. The previous chapter's iframe is removed
// 2. A new iframe is created. The initial HTML is a "blank" page
// containing (invisible) content data for the new chapter as
// text in the page.
// 3. The content data is posted to the server via a form
// submission, which returns the decoded HTML.
//
// The client should only inject into the new frame after step 3.
this.contentFrame.src = 'about:blank';
setTimeout(() => {
// Set the final URL in a way that doesn't update the `src` attribute
// of the iframe, to make sure the client isn't relying on that.
this.contentFrame.contentWindow.location.href = chapterURL;
}, 50);
}
};
const styles = document.createElement('style'); const styles = document.createElement('style');
styles.innerHTML = ` styles.innerHTML = `
iframe { iframe {
width: 100%; width: 100%;
height: 400px; height: 500px;
resize: both; resize: both;
overflow: auto; overflow: auto;
} }
...@@ -74,14 +43,54 @@ export class MosaicBookElement extends HTMLElement { ...@@ -74,14 +43,54 @@ export class MosaicBookElement extends HTMLElement {
this.prevButton = document.createElement('button'); this.prevButton = document.createElement('button');
this.prevButton.textContent = 'Prev chapter'; this.prevButton.textContent = 'Prev chapter';
this.prevButton.onclick = () => setChapter(chapterIndex - 1); this.prevButton.onclick = () => this.setChapter(this.chapterIndex - 1);
controlBar.append(this.prevButton); controlBar.append(this.prevButton);
this.nextButton = document.createElement('button'); this.nextButton = document.createElement('button');
this.nextButton.textContent = 'Next chapter'; this.nextButton.textContent = 'Next chapter';
this.nextButton.onclick = () => setChapter(chapterIndex + 1); this.nextButton.onclick = () => this.setChapter(this.chapterIndex + 1);
controlBar.append(this.nextButton); controlBar.append(this.nextButton);
setChapter(0, { initialLoad: true }); this.setChapter(0, { initialLoad: true });
}
setChapter(index, { initialLoad = false } = {}) {
if (index < 0 || index >= this.chapterURLs.length) {
return;
}
this.chapterIndex = index;
// We remove the current frame and create a new one, rather than just
// change the `src` of the existing iframe, to mimic what Bookshelf
// does. The client should be robust to either approach.
this.contentFrame?.remove();
this.contentFrame = document.createElement('iframe');
this.shadowRoot.append(this.contentFrame);
const chapterURL = this.chapterURLs[this.chapterIndex];
if (initialLoad) {
// Simulate client loading after VS chapter content has already
// loaded.
this.contentFrame.src = chapterURL;
} else {
// Simulate chapter navigation after client is injected. These
// navigations happen in several stages:
//
// 1. The previous chapter's iframe is removed
// 2. A new iframe is created. The initial HTML is a "blank" page
// containing (invisible) content data for the new chapter as
// text in the page.
// 3. The content data is posted to the server via a form
// submission, which returns the decoded HTML.
//
// The client should only inject into the new frame after step 3.
this.contentFrame.src = 'about:blank';
setTimeout(() => {
// Set the final URL in a way that doesn't update the `src` attribute
// of the iframe, to make sure the client isn't relying on that.
this.contentFrame.contentWindow.location.href = chapterURL;
}, 50);
}
} }
} }
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