[improve] web reader activity & progress tracking
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Evan Reichard 2023-11-03 23:43:08 -04:00
parent 985b6e0851
commit 71898c39e7

View File

@ -73,7 +73,6 @@ function populateMetadata(data) {
**/ **/
class EBookReader { class EBookReader {
bookState = { bookState = {
currentWord: 0,
pages: 0, pages: 0,
percentage: 0, percentage: 0,
progress: "", progress: "",
@ -115,22 +114,18 @@ class EBookReader {
* Load progress and generate locations * Load progress and generate locations
**/ **/
async setupReader() { async setupReader() {
// Get Word Count (If Needed) // Get Word Count
if (this.bookState.words == 0) this.bookState.words = await this.countWords();
this.bookState.words = await this.countWords();
// Load Progress // Load Progress
let { cfi } = await this.getCFIFromXPath(this.bookState.progress); let { cfi } = await this.getCFIFromXPath(this.bookState.progress);
this.bookState.currentWord = cfi
? this.bookState.percentage * (this.bookState.words / 100)
: 0;
let getStats = function () { let getStats = async function () {
// Start Timer // Start Timer
this.bookState.pageStart = Date.now(); this.bookState.pageStart = Date.now();
// Get Stats // Get Stats
let stats = this.getBookStats(); let stats = await this.getBookStats();
this.updateBookStatElements(stats); this.updateBookStatElements(stats);
}.bind(this); }.bind(this);
@ -660,7 +655,7 @@ class EBookReader {
this.bookState.pageStart = Date.now(); this.bookState.pageStart = Date.now();
// Update Stats // Update Stats
let stats = this.getBookStats(); let stats = await this.getBookStats();
this.updateBookStatElements(stats); this.updateBookStatElements(stats);
// Create Progress // Create Progress
@ -674,16 +669,11 @@ class EBookReader {
// Render Previous Page // Render Previous Page
await this.rendition.prev(); await this.rendition.prev();
// Update Current Word
let pageWords = await this.getVisibleWordCount();
this.bookState.currentWord -= pageWords;
if (this.bookState.currentWord < 0) this.bookState.currentWord = 0;
// Reset Read Timer // Reset Read Timer
this.bookState.pageStart = Date.now(); this.bookState.pageStart = Date.now();
// Update Stats // Update Stats
let stats = this.getBookStats(); let stats = await this.getBookStats();
this.updateBookStatElements(stats); this.updateBookStatElements(stats);
// Create Progress // Create Progress
@ -719,9 +709,8 @@ class EBookReader {
// Update Current Word // Update Current Word
let pageWords = await this.getVisibleWordCount(); let pageWords = await this.getVisibleWordCount();
let startingWord = this.bookState.currentWord; let currentWord = await this.getBookWordPosition();
let percentRead = pageWords / this.bookState.words; let percentRead = pageWords / this.bookState.words;
this.bookState.currentWord += pageWords;
let pageWPM = pageWords / (elapsedTime / 60000); let pageWPM = pageWords / (elapsedTime / 60000);
console.log("[createActivity] Page WPM:", pageWPM); console.log("[createActivity] Page WPM:", pageWPM);
@ -740,10 +729,10 @@ class EBookReader {
// Exclude 0 Pages // Exclude 0 Pages
if (totalPages == 0) if (totalPages == 0)
return console.log("[createActivity] Invalid Total Pages (0)"); return console.warn("[createActivity] Invalid Total Pages (0)");
let currentPage = Math.round( let currentPage = Math.round(
(startingWord * totalPages) / this.bookState.words (currentWord * totalPages) / this.bookState.words
); );
// Create Activity Event // Create Activity Event
@ -805,6 +794,8 @@ class EBookReader {
// Update Pointers // Update Pointers
let currentCFI = await this.rendition.currentLocation(); let currentCFI = await this.rendition.currentLocation();
let { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi); let { element, xpath } = await this.getXPathFromCFI(currentCFI.start.cfi);
let currentWord = await this.getBookWordPosition();
console.log("[createProgress] Current Word:", currentWord);
this.bookState.progress = xpath; this.bookState.progress = xpath;
this.bookState.progressElement = element; this.bookState.progressElement = element;
@ -814,9 +805,7 @@ class EBookReader {
device_id: this.readerSettings.deviceID, device_id: this.readerSettings.deviceID,
device: this.readerSettings.deviceName, device: this.readerSettings.deviceName,
percentage: percentage:
Math.round( Math.round((currentWord / this.bookState.words) * 100000) / 100000,
(this.bookState.currentWord / this.bookState.words) * 100000
) / 100000,
progress: this.bookState.progress, progress: this.bookState.progress,
}; };
@ -885,12 +874,13 @@ class EBookReader {
/** /**
* Get chapter pages, name and progress percentage * Get chapter pages, name and progress percentage
**/ **/
getBookStats() { async getBookStats() {
let currentProgress = this.sectionProgress(); let currentProgress = this.sectionProgress();
if (!currentProgress) return; if (!currentProgress) return;
let { sectionPages, sectionCurrentPage } = currentProgress; let { sectionPages, sectionCurrentPage } = currentProgress;
let currentLocation = this.rendition.currentLocation(); let currentLocation = this.rendition.currentLocation();
let currentWord = await this.getBookWordPosition();
let currentTOC = this.book.navigation.toc.find( let currentTOC = this.book.navigation.toc.find(
(item) => item.href == currentLocation.start.href (item) => item.href == currentLocation.start.href
@ -901,9 +891,7 @@ class EBookReader {
sectionTotalPages: sectionPages, sectionTotalPages: sectionPages,
chapterName: currentTOC ? currentTOC.label.trim() : "N/A", chapterName: currentTOC ? currentTOC.label.trim() : "N/A",
percentage: percentage:
Math.round( Math.round((currentWord / this.bookState.words) * 10000) / 100,
(this.bookState.currentWord / this.bookState.words) * 10000
) / 100,
}; };
} }
@ -1076,6 +1064,43 @@ class EBookReader {
* Get visible word count - used for reading stats * Get visible word count - used for reading stats
**/ **/
async getVisibleWordCount() { async getVisibleWordCount() {
let visibleText = await this.getVisibleText();
return visibleText.trim().split(/\s+/).length;
}
/**
* Gets the word number of the whole book for the first visible word.
**/
async getBookWordPosition() {
// Get Contents & Spine
let contents = this.rendition.getContents()[0];
let spineItem = this.book.spine.get(contents.sectionIndex);
// Get CFI Range
let firstCFI = spineItem.cfiFromElement(
spineItem.document.body.children[0]
);
let currentLocation = await this.rendition.currentLocation();
let cfiRange = this.getCFIRange(firstCFI, currentLocation.start.cfi);
// Get Chapter Text (Before Current Position)
let textRange = await this.book.getRange(cfiRange);
let chapterText = textRange.toString();
// Get Chapter & Book Positions
let chapterWordPosition = chapterText.trim().split(/\s+/).length;
let preChapterWordPosition = this.book.spine.spineItems
.slice(0, contents.sectionIndex)
.reduce((totalCount, item) => totalCount + item.wordCount, 0);
// Return Current Word Pointer
return chapterWordPosition + preChapterWordPosition;
}
/**
* Get visible text - used for word counts
**/
async getVisibleText() {
// Force Expand & Resize (Race Condition Issue) // Force Expand & Resize (Race Condition Issue)
this.rendition.manager.visible().forEach((item) => item.expand()); this.rendition.manager.visible().forEach((item) => item.expand());
@ -1092,7 +1117,7 @@ class EBookReader {
let visibleText = textRange.toString(); let visibleText = textRange.toString();
// Split on Whitespace // Split on Whitespace
return visibleText.trim().split(/\s+/).length; return visibleText;
} }
/** /**
@ -1147,14 +1172,17 @@ class EBookReader {
* of progress percentage. Implementation returns the same number as the * of progress percentage. Implementation returns the same number as the
* server side implementation. * server side implementation.
**/ **/
countWords() { async countWords() {
// Iterate over each item in the spine, render, and count words. let spineWC = await Promise.all(
return this.book.spine.spineItems.reduce(async (totalCount, item) => { this.book.spine.spineItems.map(async (item) => {
let currentCount = await totalCount; let newDoc = await item.load(this.book.load.bind(this.book));
let newDoc = await item.load(this.book.load.bind(this.book)); let spineWords = newDoc.innerText.trim().split(/\s+/).length;
let itemCount = newDoc.innerText.trim().split(/\s+/).length; item.wordCount = spineWords;
return currentCount + itemCount; return spineWords;
}, 0); })
);
return spineWC.reduce((totalCount, itemCount) => totalCount + itemCount, 0);
} }
/** /**