Paging is broken.

This commit is contained in:
Joseph Milazzo 2025-07-05 14:29:35 -05:00
parent 5ca7bf1a87
commit f3a1933247
6 changed files with 161 additions and 155 deletions

View file

@ -19,7 +19,6 @@ import {
EpubSettingDrawerComponent, EpubSettingDrawerComponent,
} from "../book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component"; } from "../book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component";
import {ReadingProfile} from "../_models/preferences/reading-profiles"; import {ReadingProfile} from "../_models/preferences/reading-profiles";
import {ReaderSettingUpdate} from "./epub-reader-settings.service";
/** /**
* Responsible for opening the different readers and providing any context needed. Handles closing or keeping a stack of menus open. * Responsible for opening the different readers and providing any context needed. Handles closing or keeping a stack of menus open.
@ -91,7 +90,7 @@ export class EpubReaderMenuService {
} }
openSettingsDrawer(chapterId: number, seriesId: number, readingProfile: ReadingProfile, callbackFn: (evt: ReaderSettingUpdate) => void) { openSettingsDrawer(chapterId: number, seriesId: number, readingProfile: ReadingProfile) {
if (this.offcanvasService.hasOpenOffcanvas()) { if (this.offcanvasService.hasOpenOffcanvas()) {
this.offcanvasService.dismiss(); this.offcanvasService.dismiss();
} }
@ -100,13 +99,6 @@ export class EpubReaderMenuService {
ref.componentInstance.seriesId.set(seriesId); ref.componentInstance.seriesId.set(seriesId);
ref.componentInstance.readingProfile.set(readingProfile); ref.componentInstance.readingProfile.set(readingProfile);
// ref.componentInstance.updated.subscribe((res: ReaderSettingUpdate) => {
// // Check if we are on mobile to collapse the menu
// if (this.utilityService.activeUserBreakpoint() <= UserBreakpoint.Mobile) {
// this.closeAll();
// }
// callbackFn(res);
// });
ref.closed.subscribe(() => this.setDrawerClosed()); ref.closed.subscribe(() => this.setDrawerClosed());
ref.dismissed.subscribe(() => this.setDrawerClosed()); ref.dismissed.subscribe(() => this.setDrawerClosed());

View file

@ -10,7 +10,7 @@
[volumeId]="volumeId" [volumeId]="volumeId"
[chapterId]="chapterId" [chapterId]="chapterId"
[seriesId]="seriesId" [seriesId]="seriesId"
[pageNumber]="pageNum" [pageNumber]="pageNum()"
(isOpen)="updateLineOverlayOpen($event)" (isOpen)="updateLineOverlayOpen($event)"
(refreshToC)="refreshPersonalToC()"> (refreshToC)="refreshPersonalToC()">
</app-book-line-overlay> </app-book-line-overlay>
@ -40,7 +40,7 @@
@if (page !== undefined) { @if (page !== undefined) {
<div #readingHtml class="book-content {{layoutMode() | columnLayoutClass}} {{writingStyle() | writingStyleClass}}" <div #readingHtml class="book-content {{layoutMode() | columnLayoutClass}} {{writingStyle() | writingStyleClass}}"
[ngStyle]="{'max-height': ColumnHeight, 'max-width': VerticalBookContentWidth, 'width': VerticalBookContentWidth, 'column-width': ColumnWidth}" [ngStyle]="{'max-height': columnHeight(), 'max-width': verticalBookContentWidth(), 'width': verticalBookContentWidth(), 'column-width': columnWidth()}"
[ngClass]="{'immersive': immersiveMode() && actionBarVisible}" [ngClass]="{'immersive': immersiveMode() && actionBarVisible}"
[innerHtml]="page" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div> [innerHtml]="page" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div>
@ -128,10 +128,17 @@
<span class="visually-hidden">{{t('loading-book')}}</span> <span class="visually-hidden">{{t('loading-book')}}</span>
</div> </div>
} @else { } @else {
@let timeLeft = readingTimeLeft();
20% complete, <span class="me-1">
{{t('page-num-label', {page: pageNum()})}} / {{maxPages}}
</span>
<span> {{t('completion-label', {percent: (pageNum() / maxPages) | percent})}}</span>
@let timeLeft = readingTimeLeft();
@if (timeLeft) { @if (timeLeft) {
,
<span class="time-left" [ngbTooltip]="t('time-left-alt')"> <span class="time-left" [ngbTooltip]="t('time-left-alt')">
<i class="fa-solid fa-clock" aria-hidden="true"></i> <i class="fa-solid fa-clock" aria-hidden="true"></i>
{{timeLeft | readTimeLeft }} {{timeLeft | readTimeLeft }}

View file

@ -367,6 +367,7 @@ $pagination-opacity: 0;
border: none !important; border: none !important;
opacity: 0; opacity: 0;
outline: none; outline: none;
cursor: pointer;
&.immersive { &.immersive {
top: 0px; top: 0px;

View file

@ -3,6 +3,7 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
computed,
DestroyRef, DestroyRef,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
@ -14,10 +15,11 @@ import {
OnInit, OnInit,
Renderer2, Renderer2,
RendererStyleFlags2, RendererStyleFlags2,
Signal,
ViewChild, ViewChild,
ViewContainerRef ViewContainerRef
} from '@angular/core'; } from '@angular/core';
import {DOCUMENT, NgClass, NgStyle, NgTemplateOutlet} from '@angular/common'; import {DOCUMENT, NgClass, NgStyle, NgTemplateOutlet, PercentPipe} from '@angular/common';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {ToastrService} from 'ngx-toastr'; import {ToastrService} from 'ngx-toastr';
import {forkJoin, fromEvent, merge, of} from 'rxjs'; import {forkJoin, fromEvent, merge, of} from 'rxjs';
@ -103,7 +105,7 @@ const elementLevelStyles = ['line-height', 'font-family'];
]) ])
], ],
imports: [NgTemplateOutlet, NgStyle, NgClass, NgbTooltip, imports: [NgTemplateOutlet, NgStyle, NgClass, NgbTooltip,
BookLineOverlayComponent, TranslocoDirective, ColumnLayoutClassPipe, WritingStyleClassPipe, ReadTimeLeftPipe] BookLineOverlayComponent, TranslocoDirective, ColumnLayoutClassPipe, WritingStyleClassPipe, ReadTimeLeftPipe, PercentPipe]
}) })
export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
@ -161,7 +163,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
/** /**
* Current Page * Current Page
*/ */
pageNum = 0; pageNum = model<number>(0);
/** /**
* Max Pages * Max Pages
*/ */
@ -298,8 +300,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
/** /**
* Width of the document (in non-column layout), used for column layout virtual paging * Width of the document (in non-column layout), used for column layout virtual paging
*/ */
windowWidth: number = 0; windowWidth = model<number>(0);
windowHeight: number = 0; windowHeight = model<number>(0);
/** /**
* used to track if a click is a drag or not, for opening menu * used to track if a click is a drag or not, for opening menu
@ -349,6 +351,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
protected readonly writingStyle = this.readerSettingsService.writingStyle; protected readonly writingStyle = this.readerSettingsService.writingStyle;
protected readonly clickToPaginate = this.readerSettingsService.clickToPaginate; protected readonly clickToPaginate = this.readerSettingsService.clickToPaginate;
protected columnWidth!: Signal<string>;
protected columnHeight!: Signal<string>;
protected verticalBookContentWidth!: Signal<string>;
/** /**
* Disables the Left most button * Disables the Left most button
*/ */
@ -373,7 +379,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
isNextPageDisabled() { isNextPageDisabled() {
const [currentVirtualPage, totalVirtualPages, _] = this.getVirtualPage(); const [currentVirtualPage, totalVirtualPages, _] = this.getVirtualPage();
const condition = (this.nextPageDisabled || this.nextChapterId === CHAPTER_ID_DOESNT_EXIST) && this.pageNum + 1 > this.maxPages - 1; const condition = (this.nextPageDisabled || this.nextChapterId === CHAPTER_ID_DOESNT_EXIST) && this.pageNum() + 1 > this.maxPages - 1;
if (this.layoutMode() !== BookPageLayoutMode.Default) { if (this.layoutMode() !== BookPageLayoutMode.Default) {
return condition && currentVirtualPage === totalVirtualPages; return condition && currentVirtualPage === totalVirtualPages;
} }
@ -382,7 +388,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
isPrevPageDisabled() { isPrevPageDisabled() {
const [currentVirtualPage,,] = this.getVirtualPage(); const [currentVirtualPage,,] = this.getVirtualPage();
const condition = (this.prevPageDisabled || this.prevChapterId === CHAPTER_ID_DOESNT_EXIST) && this.pageNum === 0; const condition = (this.prevPageDisabled || this.prevChapterId === CHAPTER_ID_DOESNT_EXIST) && this.pageNum() === 0;
if (this.layoutMode() !== BookPageLayoutMode.Default) { if (this.layoutMode() !== BookPageLayoutMode.Default) {
return condition && currentVirtualPage === 0; return condition && currentVirtualPage === 0;
} }
@ -394,58 +400,36 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/ */
get IsNextChapter(): boolean { get IsNextChapter(): boolean {
if (this.layoutMode() === BookPageLayoutMode.Default) { if (this.layoutMode() === BookPageLayoutMode.Default) {
return this.pageNum + 1 >= this.maxPages; return this.pageNum() + 1 >= this.maxPages;
} }
const [currentVirtualPage, totalVirtualPages, _] = this.getVirtualPage(); const [currentVirtualPage, totalVirtualPages, _] = this.getVirtualPage();
if (this.bookContentElemRef == null) return this.pageNum + 1 >= this.maxPages; if (this.bookContentElemRef == null) return this.pageNum() + 1 >= this.maxPages;
return this.pageNum + 1 >= this.maxPages && (currentVirtualPage === totalVirtualPages); return this.pageNum() + 1 >= this.maxPages && (currentVirtualPage === totalVirtualPages);
} }
/** /**
* Determines if we show << or < * Determines if we show << or <
*/ */
get IsPrevChapter(): boolean { get IsPrevChapter(): boolean {
if (this.layoutMode() === BookPageLayoutMode.Default) { if (this.layoutMode() === BookPageLayoutMode.Default) {
return this.pageNum === 0; return this.pageNum() === 0;
} }
const [currentVirtualPage,,] = this.getVirtualPage(); const [currentVirtualPage,,] = this.getVirtualPage();
if (this.bookContentElemRef == null) return this.pageNum + 1 >= this.maxPages; if (this.bookContentElemRef == null) return this.pageNum() + 1 >= this.maxPages;
return this.pageNum === 0 && (currentVirtualPage === 0); return this.pageNum() === 0 && (currentVirtualPage === 0);
} }
get ColumnWidth() {
const base = this.writingStyle() === WritingStyle.Vertical ? this.windowHeight : this.windowWidth;
switch (this.layoutMode()) {
case BookPageLayoutMode.Default:
return 'unset';
case BookPageLayoutMode.Column1:
return ((base / 2) - 4) + 'px';
case BookPageLayoutMode.Column2:
return (base / 4) + 'px';
default:
return 'unset';
}
}
get ColumnHeight() { // get VerticalBookContentWidth() {
if (this.layoutMode() !== BookPageLayoutMode.Default || this.writingStyle() === WritingStyle.Vertical) { // if (this.layoutMode() !== BookPageLayoutMode.Default && this.writingStyle() !== WritingStyle.Horizontal ) {
// Take the height after page loads, subtract the top/bottom bar // const width = this.getVerticalPageWidth()
const height = this.windowHeight - (this.topOffset * 2); // return width + 'px';
return height + 'px'; // }
} // return '';
return 'unset'; // }
}
get VerticalBookContentWidth() {
if (this.layoutMode() !== BookPageLayoutMode.Default && this.writingStyle() !== WritingStyle.Horizontal ) {
const width = this.getVerticalPageWidth()
return width + 'px';
}
return '';
}
get PageWidthForPagination() { get PageWidthForPagination() {
@ -456,17 +440,21 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
get PageHeightForPagination() { get PageHeightForPagination() {
if (this.layoutMode() === BookPageLayoutMode.Default) { const layoutMode = this.layoutMode();
const immersiveMode = this.immersiveMode();
const widthHeight = this.windowHeight();
if (layoutMode=== BookPageLayoutMode.Default) {
// if the book content is less than the height of the container, override and return height of container for pagination area // if the book content is less than the height of the container, override and return height of container for pagination area
if (this.bookContainerElemRef?.nativeElement?.clientHeight > this.bookContentElemRef?.nativeElement?.clientHeight) { if (this.bookContainerElemRef?.nativeElement?.clientHeight > this.bookContentElemRef?.nativeElement?.clientHeight) {
return (this.bookContainerElemRef?.nativeElement?.clientHeight || 0) + 'px'; return (this.bookContainerElemRef?.nativeElement?.clientHeight || 0) + 'px';
} }
return (this.bookContentElemRef?.nativeElement?.scrollHeight || 0) - ((this.topOffset * (this.immersiveMode() ? 0 : 1)) * 2) + 'px'; return (this.bookContentElemRef?.nativeElement?.scrollHeight || 0) - ((this.topOffset * (immersiveMode ? 0 : 1)) * 2) + 'px';
} }
if (this.immersiveMode()) return this.windowHeight + 'px'; if (immersiveMode) return widthHeight + 'px';
return (this.windowHeight) - (this.topOffset * 2) + 'px'; return (widthHeight) - (this.topOffset * 2) + 'px';
} }
constructor(@Inject(DOCUMENT) private document: Document) { constructor(@Inject(DOCUMENT) private document: Document) {
@ -474,6 +462,49 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.navService.hideSideNav(); this.navService.hideSideNav();
this.themeService.clearThemes(); this.themeService.clearThemes();
this.cdRef.markForCheck(); this.cdRef.markForCheck();
this.columnWidth = computed(() => {
const base = this.writingStyle() === WritingStyle.Vertical ? this.windowHeight() : this.windowWidth();
switch (this.layoutMode()) {
case BookPageLayoutMode.Default:
return 'unset';
case BookPageLayoutMode.Column1:
return ((base / 2) - 4) + 'px';
case BookPageLayoutMode.Column2:
return (base / 4) + 'px';
default:
return 'unset';
}
});
this.columnHeight = computed(() => {
// Note: Computed signals need to be called before if statement to ensure it's called when a dep signal is updated
const layoutMode = this.layoutMode();
const writingStyle = this.writingStyle();
const windowHeight = this.windowHeight();
if (layoutMode !== BookPageLayoutMode.Default || writingStyle === WritingStyle.Vertical) {
// Take the height after page loads, subtract the top/bottom bar
const height = windowHeight - (this.topOffset * 2);
return height + 'px';
}
return 'unset';
});
this.verticalBookContentWidth = computed(() => {
const layoutMode = this.layoutMode();
const writingStyle = this.writingStyle();
const pageStyles = this.pageStyles(); // Needed in inner method (not sure if Signals handle)
if (layoutMode !== BookPageLayoutMode.Default && writingStyle !== WritingStyle.Horizontal ) {
const width = this.getVerticalPageWidth()
return width + 'px';
}
return '';
});
} }
/** /**
@ -549,9 +580,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
saveProgress() { saveProgress() {
let tempPageNum = this.pageNum; let tempPageNum = this.pageNum();
if (this.pageNum == this.maxPages - 1) { if (this.pageNum() == this.maxPages - 1) {
tempPageNum = this.pageNum + 1; tempPageNum = this.pageNum() + 1;
} }
if (!this.incognitoMode) { if (!this.incognitoMode) {
@ -645,29 +676,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
await this.readerSettingsService.initialize(this.seriesId, this.readingProfile); await this.readerSettingsService.initialize(this.seriesId, this.readingProfile);
// Ensure any changes in the reader settings are applied to the reader
// this.readerSettingsService.readingProfile$.pipe(
// takeUntilDestroyed(this.destroyRef)
// ).subscribe(profile => {
// if (profile) {
// this.readingProfile = profile;
// this.cdRef.markForCheck();
// }
// });
this.readerSettingsService.settingUpdates$.pipe( this.readerSettingsService.settingUpdates$.pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef),
).subscribe((update: ReaderSettingUpdate) => { tap((update) => this.handleReaderSettingsUpdate(update))
console.log('Book reader received reader setting update: ', update); ).subscribe();
this.handleReaderSettingsUpdate(update);
});
// this.readerSettingsService.activeTheme$.pipe(take(1)).subscribe(res => {
// if (!res) return;
// this.updateColorTheme(res);
// });
// TODO: I may need to do this for everything
forkJoin({ forkJoin({
chapter: this.seriesService.getChapter(this.chapterId), chapter: this.seriesService.getChapter(this.chapterId),
@ -678,7 +692,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.volumeId = results.chapter.volumeId; this.volumeId = results.chapter.volumeId;
this.maxPages = results.chapter.pages; this.maxPages = results.chapter.pages;
this.chapters = results.chapters; this.chapters = results.chapters;
this.pageNum = results.progress.pageNum; this.pageNum.set(results.progress.pageNum);
this.cdRef.markForCheck(); this.cdRef.markForCheck();
if (results.progress.bookScrollId) { if (results.progress.bookScrollId) {
@ -693,8 +707,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateImageSizes(); this.updateImageSizes();
if (this.pageNum >= this.maxPages) { if (this.pageNum() >= this.maxPages) {
this.pageNum = this.maxPages - 1; this.pageNum.set(this.maxPages - 1);
this.cdRef.markForCheck(); this.cdRef.markForCheck();
this.saveProgress(); this.saveProgress();
} }
@ -707,7 +721,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
return; return;
} }
this.setPageNum(this.pageNum); this.setPageNum(this.pageNum());
}); });
this.readerService.getPrevChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => { this.readerService.getPrevChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => {
this.prevChapterId = chapterId; this.prevChapterId = chapterId;
@ -717,7 +731,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
return; return;
} }
this.setPageNum(this.pageNum); this.setPageNum(this.pageNum());
}); });
// Check if user progress has part, if so load it so we scroll to it // Check if user progress has part, if so load it so we scroll to it
@ -765,7 +779,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} else if (event.key === KEY_CODES.G) { } else if (event.key === KEY_CODES.G) {
await this.goToPage(); await this.goToPage();
} else if (event.key === KEY_CODES.F) { } else if (event.key === KEY_CODES.F) {
this.toggleFullscreen() this.applyFullscreen()
} }
} }
@ -884,12 +898,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
if (!targetElem.attributes.hasOwnProperty('kavita-page')) { return; } if (!targetElem.attributes.hasOwnProperty('kavita-page')) { return; }
const page = parseInt(targetElem.attributes['kavita-page'].value, 10); const page = parseInt(targetElem.attributes['kavita-page'].value, 10);
if (this.adhocPageHistory.peek()?.page !== this.pageNum) { if (this.adhocPageHistory.peek()?.page !== this.pageNum()) {
this.adhocPageHistory.push({page: this.pageNum, scrollPart: this.lastSeenScrollPartPath}); this.adhocPageHistory.push({page: this.pageNum(), scrollPart: this.lastSeenScrollPartPath});
} }
const partValue = targetElem.attributes.hasOwnProperty('kavita-part') ? targetElem.attributes['kavita-part'].value : undefined; const partValue = targetElem.attributes.hasOwnProperty('kavita-part') ? targetElem.attributes['kavita-part'].value : undefined;
if (partValue && page === this.pageNum) { if (partValue && page === this.pageNum()) {
this.scrollTo(targetElem.attributes['kavita-part'].value); this.scrollTo(targetElem.attributes['kavita-part'].value);
return; return;
} }
@ -929,7 +943,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
page = parseInt(goToPageNum.trim(), 10); page = parseInt(goToPageNum.trim(), 10);
} }
if (page === undefined || this.pageNum === page) { return; } if (page === undefined || this.pageNum() === page) { return; }
if (page > this.maxPages - 1) { if (page > this.maxPages - 1) {
page = this.maxPages - 1; page = this.maxPages - 1;
@ -937,7 +951,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
page = 0; page = 0;
} }
this.pageNum = page; this.pageNum.set(page);
this.loadPage(); this.loadPage();
} }
@ -948,7 +962,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.isLoading = true; this.isLoading = true;
this.cdRef.markForCheck(); this.cdRef.markForCheck();
this.bookService.getBookPage(this.chapterId, this.pageNum).pipe(take(1)).subscribe(content => { this.bookService.getBookPage(this.chapterId, this.pageNum()).pipe(take(1)).subscribe(content => {
this.isSingleImagePage = this.checkSingleImagePage(content) // This needs be performed before we set this.page to avoid image jumping this.isSingleImagePage = this.checkSingleImagePage(content) // This needs be performed before we set this.page to avoid image jumping
this.updateSingleImagePageStyles(); this.updateSingleImagePageStyles();
this.page = this.domSanitizer.bypassSecurityTrustHtml(content); // PERF: Potential optimization to prefetch next/prev page and store in localStorage this.page = this.domSanitizer.bypassSecurityTrustHtml(content); // PERF: Potential optimization to prefetch next/prev page and store in localStorage
@ -981,7 +995,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
*/ */
updateImageSizes() { updateImageSizes() {
const isVerticalWritingStyle = this.writingStyle() === WritingStyle.Vertical; const isVerticalWritingStyle = this.writingStyle() === WritingStyle.Vertical;
const height = this.windowHeight - (this.topOffset * 2); const height = this.windowHeight() - (this.topOffset * 2);
let maxHeight = 'unset'; let maxHeight = 'unset';
let maxWidth = ''; let maxWidth = '';
switch (this.layoutMode()) { switch (this.layoutMode()) {
@ -1050,7 +1064,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// Virtual Paging stuff // Virtual Paging stuff
this.updateWidthAndHeightCalcs(); this.updateWidthAndHeightCalcs();
this.updateLayoutMode(this.layoutMode()); this.applyLayoutMode(this.layoutMode());
this.addEmptyPageIfRequired(); this.addEmptyPageIfRequired();
// Find all the part ids and their top offset // Find all the part ids and their top offset
@ -1148,7 +1162,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
const emptyPage = this.renderer.createElement('div'); const emptyPage = this.renderer.createElement('div');
this.renderer.setStyle(emptyPage, 'height', columnHeight + 'px'); this.renderer.setStyle(emptyPage, 'height', columnHeight + 'px');
this.renderer.setStyle(emptyPage, 'width', this.ColumnWidth); this.renderer.setStyle(emptyPage, 'width', this.columnHeight());
this.renderer.appendChild(this.bookContentElemRef.nativeElement, emptyPage); this.renderer.appendChild(this.bookContentElemRef.nativeElement, emptyPage);
} }
@ -1164,10 +1178,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
setPageNum(pageNum: number) { setPageNum(pageNum: number) {
this.pageNum = Math.max(Math.min(pageNum, this.maxPages), 0); this.pageNum.set(Math.max(Math.min(pageNum, this.maxPages), 0));
this.cdRef.markForCheck(); this.cdRef.markForCheck();
if (this.pageNum >= this.maxPages - 10) { if (this.pageNum() >= this.maxPages - 10) {
// Tell server to cache the next chapter // Tell server to cache the next chapter
if (!this.nextChapterPrefetched && this.nextChapterId !== CHAPTER_ID_DOESNT_EXIST) { if (!this.nextChapterPrefetched && this.nextChapterId !== CHAPTER_ID_DOESNT_EXIST) {
this.readerService.getChapterInfo(this.nextChapterId).pipe(take(1), catchError(err => { this.readerService.getChapterInfo(this.nextChapterId).pipe(take(1), catchError(err => {
@ -1179,7 +1193,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.nextChapterPrefetched = true; this.nextChapterPrefetched = true;
}); });
} }
} else if (this.pageNum <= 10) { } else if (this.pageNum() <= 10) {
if (!this.prevChapterPrefetched && this.prevChapterId !== CHAPTER_ID_DOESNT_EXIST) { if (!this.prevChapterPrefetched && this.prevChapterId !== CHAPTER_ID_DOESNT_EXIST) {
this.readerService.getChapterInfo(this.prevChapterId).pipe(take(1), catchError(err => { this.readerService.getChapterInfo(this.prevChapterId).pipe(take(1), catchError(err => {
this.prevChapterDisabled = true; this.prevChapterDisabled = true;
@ -1207,7 +1221,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
prevPage() { prevPage() {
const oldPageNum = this.pageNum; const oldPageNum = this.pageNum();
this.pagingDirection = PAGING_DIRECTION.BACKWARDS; this.pagingDirection = PAGING_DIRECTION.BACKWARDS;
@ -1227,7 +1241,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
this.setPageNum(this.pageNum - 1); this.setPageNum(this.pageNum() - 1);
if (oldPageNum === 0) { if (oldPageNum === 0) {
// Move to next volume/chapter automatically // Move to next volume/chapter automatically
@ -1235,7 +1249,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return; return;
} }
if (oldPageNum === this.pageNum) { return; } if (oldPageNum === this.pageNum()) { return; }
this.loadPage(); this.loadPage();
} }
@ -1262,7 +1276,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
const oldPageNum = this.pageNum; const oldPageNum = this.pageNum();
if (oldPageNum + 1 === this.maxPages) { if (oldPageNum + 1 === this.maxPages) {
// Move to next volume/chapter automatically // Move to next volume/chapter automatically
this.loadNextChapter(); this.loadNextChapter();
@ -1270,9 +1284,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
this.setPageNum(this.pageNum + 1); this.setPageNum(this.pageNum() + 1);
if (oldPageNum === this.pageNum) { return; } if (oldPageNum === this.pageNum()) { return; }
this.loadPage(); this.loadPage();
} }
@ -1289,7 +1303,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
getPageHeight() { getPageHeight() {
if (this.readingSectionElemRef == null) return 0; if (this.readingSectionElemRef == null) return 0;
const height = (parseInt(this.ColumnHeight.replace('px', ''), 10)); const height = (parseInt(this.columnHeight().replace('px', ''), 10));
return height - COLUMN_GAP; return height - COLUMN_GAP;
} }
@ -1379,10 +1393,6 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* Applies styles onto the html of the book page * Applies styles onto the html of the book page
*/ */
applyPageStyles(pageStyles: PageStyle) { applyPageStyles(pageStyles: PageStyle) {
console.log('Got styles from settings: ', pageStyles);
//this.pageStyles.set(pageStyles);
//this.readerSettingsService.
if (this.bookContentElemRef === undefined || !this.bookContentElemRef.nativeElement) return; if (this.bookContentElemRef === undefined || !this.bookContentElemRef.nativeElement) return;
// Before we apply styles, let's get an element on the screen so we can scroll to it after any shifts // Before we apply styles, let's get an element on the screen so we can scroll to it after any shifts
@ -1430,7 +1440,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* Applies styles and classes that control theme * Applies styles and classes that control theme
* @param theme * @param theme
*/ */
updateColorTheme(theme: BookTheme) { applyColorTheme(theme: BookTheme) {
// Remove all themes // Remove all themes
Array.from(this.document.querySelectorAll('style[id^="brtheme-"]')).forEach(elem => elem.remove()); Array.from(this.document.querySelectorAll('style[id^="brtheme-"]')).forEach(elem => elem.remove());
@ -1448,8 +1458,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
updateWidthAndHeightCalcs() { updateWidthAndHeightCalcs() {
this.windowHeight = Math.max(this.readingSectionElemRef.nativeElement.clientHeight, window.innerHeight); this.windowHeight.set(Math.max(this.readingSectionElemRef.nativeElement.clientHeight, window.innerHeight));
this.windowWidth = Math.max(this.readingSectionElemRef.nativeElement.clientWidth, window.innerWidth); this.windowWidth.set(Math.max(this.readingSectionElemRef.nativeElement.clientWidth, window.innerWidth));
// Recalculate if bottom action bar is needed // Recalculate if bottom action bar is needed
this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight; this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight;
@ -1466,22 +1476,22 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.showPaginationOverlay(res.object as boolean); this.showPaginationOverlay(res.object as boolean);
break; break;
case "fullscreen": case "fullscreen":
this.toggleFullscreen(); this.applyFullscreen();
break; break;
case "writingStyle": case "writingStyle":
this.updateWritingStyle(res.object as WritingStyle); this.applyWritingStyle(res.object as WritingStyle);
break; break;
case "layoutMode": case "layoutMode":
this.updateLayoutMode(res.object as BookPageLayoutMode); this.applyLayoutMode(res.object as BookPageLayoutMode);
break; break;
case "readingDirection": case "readingDirection":
this.updateReadingDirection(res.object as ReadingDirection); // No extra functionality needs to be done
break; break;
case "immersiveMode": case "immersiveMode":
this.updateImmersiveMode(res.object as boolean); this.applyImmersiveMode(res.object as boolean);
break; break;
case 'theme': case 'theme':
this.updateColorTheme(res.object as BookTheme); this.applyColorTheme(res.object as BookTheme);
return; return;
} }
} }
@ -1491,9 +1501,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (drawerIsOpen) { if (drawerIsOpen) {
this.epubMenuService.closeAll(); this.epubMenuService.closeAll();
} else { } else {
this.epubMenuService.openSettingsDrawer(this.chapterId, this.seriesId, this.readingProfile, (res => { this.epubMenuService.openSettingsDrawer(this.chapterId, this.seriesId, this.readingProfile);
//this.handleReaderSettingsUpdate(res);
}));
} }
if (this.immersiveMode()) { // NOTE: Shouldn't this check if drawer is open? if (this.immersiveMode()) { // NOTE: Shouldn't this check if drawer is open?
@ -1550,7 +1558,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.saveProgress(); this.saveProgress();
} }
toggleFullscreen() { applyFullscreen() {
this.isFullscreen = this.readerService.checkFullscreenMode(); this.isFullscreen = this.readerService.checkFullscreenMode();
if (this.isFullscreen) { if (this.isFullscreen) {
this.readerService.toggleFullscreen(this.reader.nativeElement, () => { this.readerService.toggleFullscreen(this.reader.nativeElement, () => {
@ -1571,8 +1579,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
updateWritingStyle(writingStyle: WritingStyle) { applyWritingStyle(writingStyle: WritingStyle) {
this.readerSettingsService.updateWritingStyle(writingStyle); //this.readerSettingsService.updateWritingStyle(writingStyle);
setTimeout(() => this.updateImageSizes()); setTimeout(() => this.updateImageSizes());
if (this.layoutMode() !== BookPageLayoutMode.Default) { if (this.layoutMode() !== BookPageLayoutMode.Default) {
const lastSelector = this.lastSeenScrollPartPath; const lastSelector = this.lastSeenScrollPartPath;
@ -1590,12 +1598,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }
updateLayoutMode(mode: BookPageLayoutMode) { applyLayoutMode(mode: BookPageLayoutMode) {
const layoutModeChanged = mode !== this.layoutMode(); //const layoutModeChanged = mode !== this.layoutMode(); // TODO: This functionality wont work on the new signal-based logic
this.readerSettingsService.updateLayoutMode(mode);
this.cdRef.markForCheck();
console.log('layout mode changed to: ', this.layoutMode());
this.clearTimeout(this.updateImageSizeTimeout); this.clearTimeout(this.updateImageSizeTimeout);
this.updateImageSizeTimeout = setTimeout( () => { this.updateImageSizeTimeout = setTimeout( () => {
@ -1605,10 +1609,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateSingleImagePageStyles() this.updateSingleImagePageStyles()
// Calculate if bottom actionbar is needed. On a timeout to get accurate heights // Calculate if bottom actionbar is needed. On a timeout to get accurate heights
if (this.bookContentElemRef == null) { // if (this.bookContentElemRef == null) {
setTimeout(() => this.updateLayoutMode(this.layoutMode()), 10); // setTimeout(() => this.applyLayoutMode(this.layoutMode()), 10);
return; // return;
} // }
setTimeout(() => { setTimeout(() => {
this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight; this.scrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientHeight > this.reader?.nativeElement?.clientHeight;
this.horizontalScrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientWidth > this.reader?.nativeElement?.clientWidth; this.horizontalScrollbarNeeded = this.bookContentElemRef?.nativeElement?.clientWidth > this.reader?.nativeElement?.clientWidth;
@ -1616,19 +1620,14 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
}); });
// When I switch layout, I might need to resume the progress point. // When I switch layout, I might need to resume the progress point.
if (mode === BookPageLayoutMode.Default && layoutModeChanged) { // if (mode === BookPageLayoutMode.Default && layoutModeChanged) {
const lastSelector = this.lastSeenScrollPartPath; // const lastSelector = this.lastSeenScrollPartPath;
setTimeout(() => this.scrollTo(lastSelector)); // setTimeout(() => this.scrollTo(lastSelector));
} // }
} }
updateReadingDirection(readingDirection: ReadingDirection) {
this.readerSettingsService.updateReadingDirection(readingDirection);
this.cdRef.markForCheck();
}
updateImmersiveMode(immersiveMode: boolean) { applyImmersiveMode(immersiveMode: boolean) {
this.readerSettingsService.updateImmersiveMode(immersiveMode);
if (immersiveMode && !this.epubMenuService.isDrawerOpen()) { if (immersiveMode && !this.epubMenuService.isDrawerOpen()) {
this.actionBarVisible = false; this.actionBarVisible = false;
this.updateReadingSectionHeight(); this.updateReadingSectionHeight();
@ -1670,7 +1669,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.pageAnchors = {}; this.pageAnchors = {};
this.currentPageAnchor = ''; this.currentPageAnchor = '';
this.cdRef.markForCheck(); this.cdRef.markForCheck();
const ids = this.chapters.map(item => item.children).flat().filter(item => item.page === this.pageNum).map(item => item.part).filter(item => item.length > 0); const ids = this.chapters.map(item => item.children).flat().filter(item => item.page === this.pageNum()).map(item => item.part).filter(item => item.length > 0);
if (ids.length > 0) { if (ids.length > 0) {
const elems = this.getPageMarkers(ids); const elems = this.getPageMarkers(ids);
elems.forEach(elem => { elems.forEach(elem => {

View file

@ -26,7 +26,9 @@
<label for="fontsize" class="form-label col-6">{{t('font-size-label')}}</label> <label for="fontsize" class="form-label col-6">{{t('font-size-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;"> <span class="col-6 float-end" style="display: inline-flex;">
<i class="fa-solid fa-font" style="font-size: 12px;"></i> <i class="fa-solid fa-font" style="font-size: 12px;"></i>
<input type="range" class="form-range ms-2 me-2" id="fontsize" min="50" max="300" step="10" formControlName="bookReaderFontSize" [ngbTooltip]="settingsForm.get('bookReaderFontSize')?.value + '%'"> <input type="range" class="form-range ms-2 me-2" id="fontsize"
min="50" max="300" step="10" formControlName="bookReaderFontSize"
[ngbTooltip]="pageStyles()['font-size']">
<i class="fa-solid fa-font" style="font-size: 24px;"></i> <i class="fa-solid fa-font" style="font-size: 24px;"></i>
</span> </span>
</div> </div>
@ -35,7 +37,9 @@
<label for="linespacing" class="form-label col-6">{{t('line-spacing-label')}}</label> <label for="linespacing" class="form-label col-6">{{t('line-spacing-label')}}</label>
<span class="col-6 float-end" style="display: inline-flex;"> <span class="col-6 float-end" style="display: inline-flex;">
{{t('line-spacing-min-label')}} {{t('line-spacing-min-label')}}
<input type="range" class="form-range ms-2 me-2" id="linespacing" min="100" max="200" step="10" formControlName="bookReaderLineSpacing" [ngbTooltip]="settingsForm.get('bookReaderLineSpacing')?.value + '%'"> <input type="range" class="form-range ms-2 me-2" id="linespacing"
min="100" max="200" step="10" formControlName="bookReaderLineSpacing"
[ngbTooltip]="pageStyles()['line-height']">
{{t('line-spacing-max-label')}} {{t('line-spacing-max-label')}}
</span> </span>
</div> </div>

View file

@ -865,7 +865,10 @@
"go-to-page-prompt": "There are {{totalPages}} pages. What page do you want to go to?", "go-to-page-prompt": "There are {{totalPages}} pages. What page do you want to go to?",
"go-to-section": "Go to section", "go-to-section": "Go to section",
"go-to-section-prompt": "There are {{totalSections}} sections. What section do you want to go to?" "go-to-section-prompt": "There are {{totalSections}} sections. What section do you want to go to?",
"page-num-label": "Page {{page}}",
"completion-label": "{{percent}} complete"
}, },
"personal-table-of-contents": { "personal-table-of-contents": {