This will be a long journey. LayoutMode isn't getting handled correctly, I need to refactor the epub service finally to signals and streamline all the state into one place.
This commit is contained in:
parent
9474e8264a
commit
d114949446
10 changed files with 186 additions and 140 deletions
|
|
@ -826,6 +826,37 @@ public class ReaderController : BaseApiController
|
|||
return _readerService.GetTimeEstimate(0, pagesLeft, false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// For the current user, returns an estimate on how long it would take to finish reading the chapter.
|
||||
/// </summary>
|
||||
/// <remarks>For Epubs, this does not check words inside a chapter due to overhead so may not work in all cases.</remarks>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("time-left-for-chapter")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["seriesId", "chapterId"])]
|
||||
public async Task<ActionResult<HourEstimateRangeDto>> GetEstimateToCompletionForChapter(int seriesId, int chapterId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
|
||||
var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId);
|
||||
if (series == null || chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
|
||||
|
||||
// Patch in the reading progress
|
||||
await _unitOfWork.ChapterRepository.AddChapterModifiers(User.GetUserId(), chapter);
|
||||
|
||||
if (series.Format == MangaFormat.Epub)
|
||||
{
|
||||
var progressCount = chapter.WordCount;
|
||||
var wordsLeft = series.WordCount - progressCount;
|
||||
return _readerService.GetTimeEstimate(wordsLeft, 0, true);
|
||||
}
|
||||
|
||||
var pagesLeft = chapter.Pages - chapter.PagesRead;
|
||||
return _readerService.GetTimeEstimate(0, pagesLeft, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the annotations for the given chapter
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -4,5 +4,4 @@ export interface IHasReadingTime {
|
|||
avgHoursToRead: number;
|
||||
pages: number;
|
||||
wordCount: number;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,13 +100,13 @@ export class EpubReaderMenuService {
|
|||
ref.componentInstance.seriesId.set(seriesId);
|
||||
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.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.dismissed.subscribe(() => this.setDrawerClosed());
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import {ReadingProfileService} from "./reading-profile.service";
|
|||
import {debounceTime, skip, tap} from "rxjs/operators";
|
||||
import {BookTheme} from "../_models/preferences/book-theme";
|
||||
import {DOCUMENT} from "@angular/common";
|
||||
import {translate} from "@jsverse/transloco";
|
||||
import {ToastrService} from "ngx-toastr";
|
||||
|
||||
export interface ReaderSettingUpdate {
|
||||
setting: 'pageStyle' | 'clickToPaginate' | 'fullscreen' | 'writingStyle' | 'layoutMode' | 'readingDirection' | 'immersiveMode' | 'theme';
|
||||
|
|
@ -28,6 +30,7 @@ export class EpubReaderSettingsService {
|
|||
private readonly bookService = inject(BookService);
|
||||
private readonly themeService = inject(ThemeService);
|
||||
private readonly readingProfileService = inject(ReadingProfileService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
private readonly document = inject(DOCUMENT);
|
||||
|
||||
private pageStylesSubject = new BehaviorSubject<PageStyle>(this.getDefaultPageStyles());
|
||||
|
|
@ -46,6 +49,7 @@ export class EpubReaderSettingsService {
|
|||
// Form and data
|
||||
private settingsForm: FormGroup = new FormGroup({});
|
||||
private currentReadingProfile: ReadingProfile | null = null;
|
||||
private parentReadingProfile: ReadingProfile | null = null;
|
||||
private currentSeriesId: number | null = null;
|
||||
private fontFamilies: FontFamily[] = this.bookService.getFontFamilies();
|
||||
|
||||
|
|
@ -69,13 +73,15 @@ export class EpubReaderSettingsService {
|
|||
async initialize(seriesId: number, readingProfile: ReadingProfile): Promise<void> {
|
||||
this.currentSeriesId = seriesId;
|
||||
this.currentReadingProfile = readingProfile;
|
||||
console.log('init, reading profile: ', readingProfile);
|
||||
this.readingProfileSubject.next(readingProfile);
|
||||
|
||||
// Load parent profile if needed
|
||||
if (readingProfile.kind === ReadingProfileKind.Implicit) {
|
||||
try {
|
||||
// const parent = await this.readingProfileService.getForSeries(seriesId, true).toPromise();
|
||||
// Keep the implicit profile but use parent as reference
|
||||
const parent = await this.readingProfileService.getForSeries(seriesId, true).toPromise();
|
||||
this.parentReadingProfile = parent || null;
|
||||
// Keep the implicit profile but use parent as reference (TODO: Validate the code)
|
||||
} catch (error) {
|
||||
console.error('Failed to load parent reading profile:', error);
|
||||
}
|
||||
|
|
@ -83,7 +89,7 @@ export class EpubReaderSettingsService {
|
|||
|
||||
// Setup defaults and form
|
||||
this.setupDefaultSettings();
|
||||
this.setupSettingsForm();
|
||||
|
||||
|
||||
// Set initial theme
|
||||
const themeName = readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme;
|
||||
|
|
@ -227,6 +233,8 @@ export class EpubReaderSettingsService {
|
|||
profile.bookReaderWritingStyle = WritingStyle.Horizontal;
|
||||
}
|
||||
|
||||
this.setupSettingsForm();
|
||||
|
||||
// Update internal state
|
||||
this.readingDirectionSubject.next(profile.bookReaderReadingDirection);
|
||||
this.writingStyleSubject.next(profile.bookReaderWritingStyle);
|
||||
|
|
@ -418,6 +426,8 @@ export class EpubReaderSettingsService {
|
|||
data.bookReaderThemeName = activeTheme.name;
|
||||
}
|
||||
|
||||
console.log('packed reading profile:', data);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -455,4 +465,16 @@ export class EpubReaderSettingsService {
|
|||
}
|
||||
|
||||
|
||||
createNewProfileFromImplicit() {
|
||||
const rp = this.getCurrentReadingProfile();
|
||||
if (rp === null || rp.kind !== ReadingProfileKind.Implicit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.promoteProfile().subscribe(newProfile => {
|
||||
this.currentReadingProfile = newProfile;
|
||||
this.parentReadingProfile = newProfile;
|
||||
this.toastr.success(translate("manga-reader.reading-profile-promoted"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,6 +224,10 @@ export class ReaderService {
|
|||
return this.httpClient.get<HourEstimateRange>(this.baseUrl + 'reader/time-left?seriesId=' + seriesId);
|
||||
}
|
||||
|
||||
getTimeLeftForChapter(seriesId: number, chapterId: number) {
|
||||
return this.httpClient.get<HourEstimateRange>(this.baseUrl + `reader/time-left-for-chapter?seriesId=${seriesId}&chapterId=${chapterId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures current body color and forces background color to be black. Call @see resetOverrideStyles() on destroy of component to revert changes
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -13,14 +13,6 @@
|
|||
<app-reader-settings
|
||||
[seriesId]="sId"
|
||||
[readingProfile]="rp"
|
||||
(colorThemeUpdate)="updateColorTheme($event)"
|
||||
(styleUpdate)="updateReaderStyles($event)"
|
||||
(clickToPaginateChanged)="showPaginationOverlay($event)"
|
||||
(fullscreen)="toggleFullscreen()"
|
||||
(bookReaderWritingStyle)="updateWritingStyle($event)"
|
||||
(layoutModeUpdate)="updateLayoutMode($event)"
|
||||
(readingDirection)="updateReadingDirection($event)"
|
||||
(immersiveMode)="updateImmersiveMode($event)"
|
||||
></app-reader-settings>
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,8 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
effect,
|
||||
EventEmitter,
|
||||
inject,
|
||||
model
|
||||
} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, inject, model} from '@angular/core';
|
||||
import {NgbActiveOffcanvas} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {PageStyle, ReaderSettingsComponent} from "../../reader-settings/reader-settings.component";
|
||||
import {ReaderSettingsComponent} from "../../reader-settings/reader-settings.component";
|
||||
import {ReadingProfile} from "../../../../_models/preferences/reading-profiles";
|
||||
import {BookTheme} from "../../../../_models/preferences/book-theme";
|
||||
import {WritingStyle} from "../../../../_models/preferences/writing-style";
|
||||
import {BookPageLayoutMode} from "../../../../_models/readers/book-page-layout-mode";
|
||||
import {ReadingDirection} from "../../../../_models/preferences/reading-direction";
|
||||
import {TranslocoDirective} from "@jsverse/transloco";
|
||||
import {ReaderSettingUpdate} from "../../../../_services/epub-reader-settings.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-epub-setting-drawer',
|
||||
|
|
@ -35,7 +22,7 @@ export class EpubSettingDrawerComponent {
|
|||
seriesId = model<number>();
|
||||
readingProfile = model<ReadingProfile>();
|
||||
|
||||
updated = new EventEmitter<ReaderSettingUpdate>();
|
||||
// updated = new EventEmitter<ReaderSettingUpdate>();
|
||||
|
||||
|
||||
constructor() {
|
||||
|
|
@ -49,46 +36,46 @@ export class EpubSettingDrawerComponent {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
updateColorTheme(theme: BookTheme) {
|
||||
const evt = {setting: 'theme', object: theme} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateReaderStyles(pageStyles: PageStyle) {
|
||||
const evt = {setting: 'pageStyle', object: pageStyles} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
showPaginationOverlay(clickToPaginate: boolean) {
|
||||
const evt = {setting: 'clickToPaginate', object: clickToPaginate} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
toggleFullscreen() {
|
||||
const evt = {setting: 'fullscreen', object: null} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateWritingStyle(writingStyle: WritingStyle) {
|
||||
const evt = {setting: 'writingStyle', object: writingStyle} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateLayoutMode(mode: BookPageLayoutMode) {
|
||||
const evt = {setting: 'layoutMode', object: mode} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateReadingDirection(readingDirection: ReadingDirection) {
|
||||
const evt = {setting: 'readingDirection', object: readingDirection} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
|
||||
updateImmersiveMode(immersiveMode: boolean) {
|
||||
const evt = {setting: 'immersiveMode', object: immersiveMode} as ReaderSettingUpdate;
|
||||
this.updated.emit(evt);
|
||||
}
|
||||
//
|
||||
// updateColorTheme(theme: BookTheme) {
|
||||
// const evt = {setting: 'theme', object: theme} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
//
|
||||
// updateReaderStyles(pageStyles: PageStyle) {
|
||||
// const evt = {setting: 'pageStyle', object: pageStyles} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
//
|
||||
// showPaginationOverlay(clickToPaginate: boolean) {
|
||||
// const evt = {setting: 'clickToPaginate', object: clickToPaginate} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
//
|
||||
// toggleFullscreen() {
|
||||
// const evt = {setting: 'fullscreen', object: null} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
//
|
||||
// updateWritingStyle(writingStyle: WritingStyle) {
|
||||
// const evt = {setting: 'writingStyle', object: writingStyle} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
//
|
||||
// updateLayoutMode(mode: BookPageLayoutMode) {
|
||||
// const evt = {setting: 'layoutMode', object: mode} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
//
|
||||
// updateReadingDirection(readingDirection: ReadingDirection) {
|
||||
// const evt = {setting: 'readingDirection', object: readingDirection} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
//
|
||||
// updateImmersiveMode(immersiveMode: boolean) {
|
||||
// const evt = {setting: 'immersiveMode', object: immersiveMode} as ReaderSettingUpdate;
|
||||
// this.updated.emit(evt);
|
||||
// }
|
||||
|
||||
close() {
|
||||
this.activeOffcanvas.close();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="container-flex {{darkMode ? 'dark-mode' : ''}} reader-container {{layoutMode | columnLayoutClass}} {{writingStyle | writingStyleClass}}"
|
||||
<div class="container-flex {{darkMode() ? 'dark-mode' : ''}} reader-container {{layoutMode() | columnLayoutClass}} {{writingStyle | writingStyleClass}}"
|
||||
tabindex="0" #reader>
|
||||
<ng-container *transloco="let t; prefix: 'book-reader'">
|
||||
<div class="fixed-top" #stickyTop>
|
||||
|
|
@ -17,35 +17,35 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
<div #readingSection class="reading-section {{layoutMode | columnLayoutClass}} {{writingStyle | writingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}"
|
||||
[ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading" (click)="handleReaderClick($event)">
|
||||
<div #readingSection class="reading-section {{layoutMode() | columnLayoutClass}} {{writingStyle | writingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}"
|
||||
[ngClass]="{'immersive' : immersiveMode() || !actionBarVisible}" [@isLoading]="isLoading" (click)="handleReaderClick($event)">
|
||||
|
||||
@if (clickToPaginate && !hidePagination) {
|
||||
<div class="left {{clickOverlayClass('left')}} no-observe"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
[ngClass]="{'immersive' : immersiveMode()}"
|
||||
tabindex="-1"
|
||||
[ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
|
||||
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
[ngClass]="{'immersive' : immersiveMode()}"
|
||||
tabindex="-1"
|
||||
[ngStyle]="{height: PageHeightForPagination}"></div>
|
||||
}
|
||||
|
||||
<div #bookContainer class="book-container {{writingStyle | writingStyleClass}}"
|
||||
[ngClass]="{'immersive' : immersiveMode}"
|
||||
[ngClass]="{'immersive' : immersiveMode()}"
|
||||
(mousedown)="mouseDown($event)" >
|
||||
|
||||
@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}"
|
||||
[ngClass]="{'immersive': immersiveMode && actionBarVisible}"
|
||||
[ngClass]="{'immersive': immersiveMode() && actionBarVisible}"
|
||||
[innerHtml]="page" (click)="toggleMenu($event)" (mousedown)="mouseDown($event)" (wheel)="onWheel($event)"></div>
|
||||
|
||||
@if ((scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)) {
|
||||
@if ((scrollbarNeeded || layoutMode() !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode() === BookPageLayoutMode.Default)) {
|
||||
<div (click)="$event.stopPropagation();"
|
||||
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
|
||||
[ngClass]="{'bottom-bar': layoutMode() !== BookPageLayoutMode.Default}">
|
||||
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
|
||||
<ng-template #topActionBar>
|
||||
@if (!immersiveMode || epubMenuService.isDrawerOpen() || actionBarVisible) {
|
||||
@if (!immersiveMode() || epubMenuService.isDrawerOpen() || actionBarVisible) {
|
||||
<div class="action-bar d-flex align-items-center px-2">
|
||||
<!-- Left: Drawer toggle -->
|
||||
<button class="btn btn-secondary me-2" (click)="toggleDrawer()">
|
||||
|
|
@ -105,7 +105,7 @@
|
|||
|
||||
|
||||
<ng-template #actionBar let-isTop>
|
||||
@if (!immersiveMode || epubMenuService.isDrawerOpen() || actionBarVisible) {
|
||||
@if (!immersiveMode() || epubMenuService.isDrawerOpen() || actionBarVisible) {
|
||||
<div class="action-bar row g-0 justify-content-between">
|
||||
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
|
||||
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
|
||||
|
|
@ -127,7 +127,16 @@
|
|||
<span class="visually-hidden">{{t('loading-book')}}</span>
|
||||
</div>
|
||||
} @else {
|
||||
20% complete, 3 hrs to go
|
||||
@let timeLeft = readingTimeLeft();
|
||||
|
||||
20% complete,
|
||||
@if (timeLeft) {
|
||||
<span class="time-left" [ngbTooltip]="t('time-left-alt')">
|
||||
<i class="fa-solid fa-clock" aria-hidden="true"></i>
|
||||
{{timeLeft | readTimeLeft }}
|
||||
</span>
|
||||
}
|
||||
|
||||
}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,9 @@ import {LoadPageEvent} from "../_drawers/view-toc-drawer/view-toc-drawer.compone
|
|||
import {EpubReaderSettingsService, ReaderSettingUpdate} from "../../../_services/epub-reader-settings.service";
|
||||
import {ColumnLayoutClassPipe} from "../../_pipes/column-layout-class.pipe";
|
||||
import {WritingStyleClassPipe} from "../../_pipes/writing-style-class.pipe";
|
||||
import {ChapterService} from "../../../_services/chapter.service";
|
||||
import {ReadTimeLeftPipe} from "../../../_pipes/read-time-left.pipe";
|
||||
import {HourEstimateRange} from "../../../_models/series-detail/hour-estimate-range";
|
||||
|
||||
|
||||
interface HistoryPoint {
|
||||
|
|
@ -100,7 +103,7 @@ const elementLevelStyles = ['line-height', 'font-family'];
|
|||
])
|
||||
],
|
||||
imports: [NgTemplateOutlet, NgStyle, NgClass, NgbTooltip,
|
||||
BookLineOverlayComponent, TranslocoDirective, ColumnLayoutClassPipe, WritingStyleClassPipe]
|
||||
BookLineOverlayComponent, TranslocoDirective, ColumnLayoutClassPipe, WritingStyleClassPipe, ReadTimeLeftPipe]
|
||||
})
|
||||
export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
|
|
@ -108,6 +111,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
private readonly router = inject(Router);
|
||||
private readonly seriesService = inject(SeriesService);
|
||||
private readonly readerService = inject(ReaderService);
|
||||
private readonly chapterService = inject(ChapterService);
|
||||
private readonly renderer = inject(Renderer2);
|
||||
private readonly navService = inject(NavService);
|
||||
private readonly toastr = inject(ToastrService);
|
||||
|
|
@ -191,7 +195,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
/**
|
||||
* Book reader setting that hides the menuing system
|
||||
*/
|
||||
immersiveMode: boolean = false;
|
||||
immersiveMode = model<boolean>(false);
|
||||
/**
|
||||
* If we are loading from backend
|
||||
*/
|
||||
|
|
@ -265,7 +269,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
/**
|
||||
* Used solely for fullscreen to apply a hack
|
||||
*/
|
||||
darkMode = true;
|
||||
darkMode = model<boolean>(true);
|
||||
readingTimeLeft = model<HourEstimateRange | null>(null);
|
||||
/**
|
||||
* Anchors that map to the page number. When you click on one of these, we will load a given page up for the user.
|
||||
*/
|
||||
|
|
@ -287,7 +292,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
/**
|
||||
* How to render the page content
|
||||
*/
|
||||
layoutMode: BookPageLayoutMode = BookPageLayoutMode.Default;
|
||||
//layoutMode: BookPageLayoutMode = BookPageLayoutMode.Default;
|
||||
layoutMode = model<BookPageLayoutMode>(BookPageLayoutMode.Default);
|
||||
|
||||
/**
|
||||
* Width of the document (in non-column layout), used for column layout virtual paging
|
||||
|
|
@ -360,7 +366,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
isNextPageDisabled() {
|
||||
const [currentVirtualPage, totalVirtualPages, _] = this.getVirtualPage();
|
||||
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;
|
||||
|
|
@ -369,7 +375,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
isPrevPageDisabled() {
|
||||
const [currentVirtualPage,,] = this.getVirtualPage();
|
||||
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;
|
||||
|
|
@ -379,7 +385,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* Determines if we show >> or >
|
||||
*/
|
||||
get IsNextChapter(): boolean {
|
||||
if (this.layoutMode === BookPageLayoutMode.Default) {
|
||||
if (this.layoutMode() === BookPageLayoutMode.Default) {
|
||||
return this.pageNum + 1 >= this.maxPages;
|
||||
}
|
||||
|
||||
|
|
@ -392,7 +398,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
* Determines if we show << or <
|
||||
*/
|
||||
get IsPrevChapter(): boolean {
|
||||
if (this.layoutMode === BookPageLayoutMode.Default) {
|
||||
if (this.layoutMode() === BookPageLayoutMode.Default) {
|
||||
return this.pageNum === 0;
|
||||
}
|
||||
|
||||
|
|
@ -404,7 +410,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
get ColumnWidth() {
|
||||
const base = this.writingStyle === WritingStyle.Vertical ? this.windowHeight : this.windowWidth;
|
||||
switch (this.layoutMode) {
|
||||
switch (this.layoutMode()) {
|
||||
case BookPageLayoutMode.Default:
|
||||
return 'unset';
|
||||
case BookPageLayoutMode.Column1:
|
||||
|
|
@ -417,7 +423,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
get ColumnHeight() {
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default || this.writingStyle === WritingStyle.Vertical) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default || this.writingStyle === WritingStyle.Vertical) {
|
||||
// Take the height after page loads, subtract the top/bottom bar
|
||||
const height = this.windowHeight - (this.topOffset * 2);
|
||||
return height + 'px';
|
||||
|
|
@ -426,7 +432,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
get VerticalBookContentWidth() {
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Horizontal ) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Horizontal ) {
|
||||
const width = this.getVerticalPageWidth()
|
||||
return width + 'px';
|
||||
}
|
||||
|
|
@ -435,23 +441,23 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
|
||||
get PageWidthForPagination() {
|
||||
if (this.layoutMode === BookPageLayoutMode.Default && this.writingStyle === WritingStyle.Vertical && this.horizontalScrollbarNeeded) {
|
||||
if (this.layoutMode() === BookPageLayoutMode.Default && this.writingStyle === WritingStyle.Vertical && this.horizontalScrollbarNeeded) {
|
||||
return 'unset';
|
||||
}
|
||||
return '100%'
|
||||
}
|
||||
|
||||
get PageHeightForPagination() {
|
||||
if (this.layoutMode === BookPageLayoutMode.Default) {
|
||||
if (this.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 (this.bookContainerElemRef?.nativeElement?.clientHeight > this.bookContentElemRef?.nativeElement?.clientHeight) {
|
||||
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 * (this.immersiveMode() ? 0 : 1)) * 2) + 'px';
|
||||
}
|
||||
|
||||
if (this.immersiveMode) return this.windowHeight + 'px';
|
||||
if (this.immersiveMode()) return this.windowHeight + 'px';
|
||||
return (this.windowHeight) - (this.topOffset * 2) + 'px';
|
||||
}
|
||||
|
||||
|
|
@ -543,6 +549,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (!this.incognitoMode) {
|
||||
this.readerService.saveProgress(this.libraryId, this.seriesId, this.volumeId, this.chapterId, tempPageNum, this.lastSeenScrollPartPath).pipe(take(1)).subscribe(() => {/* No operation */});
|
||||
}
|
||||
|
||||
// TODO: TEMP: Trigger reading time calculation update
|
||||
// this.readerService.getTimeLeftForChapter(this.seriesId, this.chapterId).subscribe(c => {
|
||||
// this.readingTimeLeft.set(c);
|
||||
// });
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
|
@ -718,7 +730,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.updateImageSizes();
|
||||
|
||||
const resumeElement = this.getFirstVisibleElementXPath();
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
|
||||
this.scrollTo(resumeElement); // This works pretty well, but not perfect
|
||||
}
|
||||
}
|
||||
|
|
@ -750,7 +762,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
onWheel(event: WheelEvent) {
|
||||
// This allows the user to scroll the page horizontally without holding shift
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default || this.writingStyle !== WritingStyle.Vertical) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default || this.writingStyle !== WritingStyle.Vertical) {
|
||||
return;
|
||||
}
|
||||
if (event.deltaY !== 0) {
|
||||
|
|
@ -966,7 +978,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
const height = this.windowHeight - (this.topOffset * 2);
|
||||
let maxHeight = 'unset';
|
||||
let maxWidth = '';
|
||||
switch (this.layoutMode) {
|
||||
switch (this.layoutMode()) {
|
||||
case BookPageLayoutMode.Default:
|
||||
if (isVerticalWritingStyle) {
|
||||
maxHeight = `${height}px`;
|
||||
|
|
@ -995,7 +1007,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
updateSingleImagePageStyles() {
|
||||
if (this.isSingleImagePage && this.layoutMode !== BookPageLayoutMode.Default) {
|
||||
if (this.isSingleImagePage && this.layoutMode() !== BookPageLayoutMode.Default) {
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-position', 'absolute');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-top', '50%');
|
||||
this.document.documentElement.style.setProperty('--book-reader-content-left', '50%');
|
||||
|
|
@ -1032,7 +1044,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
// Virtual Paging stuff
|
||||
this.updateWidthAndHeightCalcs();
|
||||
this.updateLayoutMode(this.layoutMode);
|
||||
this.updateLayoutMode(this.layoutMode());
|
||||
this.addEmptyPageIfRequired();
|
||||
|
||||
// Find all the part ids and their top offset
|
||||
|
|
@ -1043,11 +1055,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.scrollTo(part);
|
||||
} else if (scrollTop !== undefined && scrollTop !== 0) {
|
||||
setTimeout(() => this.scrollService.scrollTo(scrollTop, this.reader.nativeElement));
|
||||
} else if ((this.writingStyle === WritingStyle.Vertical) && (this.layoutMode === BookPageLayoutMode.Default)) {
|
||||
} else if ((this.writingStyle === WritingStyle.Vertical) && (this.layoutMode() === BookPageLayoutMode.Default)) {
|
||||
setTimeout(()=> this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.clientWidth, this.reader.nativeElement));
|
||||
} else {
|
||||
|
||||
if (this.layoutMode === BookPageLayoutMode.Default) {
|
||||
if (this.layoutMode() === BookPageLayoutMode.Default) {
|
||||
setTimeout(() => this.scrollService.scrollTo(0, this.reader.nativeElement));
|
||||
} else if (this.writingStyle === WritingStyle.Vertical) {
|
||||
if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) {
|
||||
|
|
@ -1112,7 +1124,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
private addEmptyPageIfRequired(): void {
|
||||
if (this.layoutMode !== BookPageLayoutMode.Column2 || this.isSingleImagePage) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Column2 || this.isSingleImagePage) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1194,7 +1206,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.pagingDirection = PAGING_DIRECTION.BACKWARDS;
|
||||
|
||||
// We need to handle virtual paging before we increment the actual page
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default) {
|
||||
const [currentVirtualPage, _, pageWidth] = this.getVirtualPage();
|
||||
|
||||
if (currentVirtualPage > 1) {
|
||||
|
|
@ -1229,7 +1241,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
this.pagingDirection = PAGING_DIRECTION.FORWARD;
|
||||
// We need to handle virtual paging before we increment the actual page
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default) {
|
||||
const [currentVirtualPage, totalVirtualPages, pageWidth] = this.getVirtualPage();
|
||||
|
||||
if (currentVirtualPage < totalVirtualPages) {
|
||||
|
|
@ -1401,7 +1413,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
// After layout shifts, we need to refocus the scroll bar
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default && resumeElement !== null && resumeElement !== undefined) {
|
||||
this.updateWidthAndHeightCalcs();
|
||||
this.scrollTo(resumeElement); // This works pretty well, but not perfect
|
||||
}
|
||||
|
|
@ -1415,7 +1427,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
// Remove all themes
|
||||
Array.from(this.document.querySelectorAll('style[id^="brtheme-"]')).forEach(elem => elem.remove());
|
||||
|
||||
this.darkMode = theme.isDarkTheme;
|
||||
this.darkMode.set(theme.isDarkTheme);
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
const styleElem = this.renderer.createElement('style');
|
||||
styleElem.id = theme.selector;
|
||||
|
|
@ -1471,13 +1484,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
if (drawerIsOpen) {
|
||||
this.epubMenuService.closeAll();
|
||||
} else {
|
||||
// TODO: BUG: Migrating the reader settings over into the drawer means default settings aren't being set on load
|
||||
this.epubMenuService.openSettingsDrawer(this.chapterId, this.seriesId, this.readingProfile, (res => {
|
||||
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?
|
||||
this.actionBarVisible = false;
|
||||
}
|
||||
this.cdRef.markForCheck();
|
||||
|
|
@ -1498,12 +1510,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
if (element === null) return;
|
||||
|
||||
if(this.layoutMode === BookPageLayoutMode.Default && this.writingStyle === WritingStyle.Vertical ) {
|
||||
if(this.layoutMode() === BookPageLayoutMode.Default && this.writingStyle === WritingStyle.Vertical ) {
|
||||
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const scrollLeft = element.getBoundingClientRect().left + window.scrollX - (windowWidth - element.getBoundingClientRect().width);
|
||||
setTimeout(() => this.scrollService.scrollToX(scrollLeft, this.reader.nativeElement, 'smooth'), 10);
|
||||
}
|
||||
else if ((this.layoutMode === BookPageLayoutMode.Default) && (this.writingStyle === WritingStyle.Horizontal)) {
|
||||
else if ((this.layoutMode() === BookPageLayoutMode.Default) && (this.writingStyle === WritingStyle.Horizontal)) {
|
||||
const fromTopOffset = element.getBoundingClientRect().top + window.scrollY + TOP_OFFSET;
|
||||
// We need to use a delay as webkit browsers (aka Apple devices) don't always have the document rendered by this point
|
||||
setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), 10);
|
||||
|
|
@ -1545,7 +1557,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.cdRef.markForCheck();
|
||||
// HACK: This is a bug with how browsers change the background color for fullscreen mode
|
||||
this.renderer.setStyle(this.reader.nativeElement, 'background', this.themeService.getCssVariable('--bs-body-color'));
|
||||
if (!this.darkMode) {
|
||||
if (!this.darkMode()) {
|
||||
this.renderer.setStyle(this.reader.nativeElement, 'background', 'white');
|
||||
}
|
||||
});
|
||||
|
|
@ -1555,7 +1567,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
updateWritingStyle(writingStyle: WritingStyle) {
|
||||
this.writingStyle = writingStyle;
|
||||
setTimeout(() => this.updateImageSizes());
|
||||
if (this.layoutMode !== BookPageLayoutMode.Default) {
|
||||
if (this.layoutMode() !== BookPageLayoutMode.Default) {
|
||||
const lastSelector = this.lastSeenScrollPartPath;
|
||||
setTimeout(() => {
|
||||
this.scrollTo(lastSelector);
|
||||
|
|
@ -1572,11 +1584,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
updateLayoutMode(mode: BookPageLayoutMode) {
|
||||
const layoutModeChanged = mode !== this.layoutMode;
|
||||
this.layoutMode = mode;
|
||||
const layoutModeChanged = mode !== this.layoutMode();
|
||||
this.layoutMode.set(mode);
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
console.log('layout mode changed to: ', this.layoutMode);
|
||||
console.log('layout mode changed to: ', this.layoutMode());
|
||||
|
||||
this.clearTimeout(this.updateImageSizeTimeout);
|
||||
this.updateImageSizeTimeout = setTimeout( () => {
|
||||
|
|
@ -1587,7 +1599,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
// Calculate if bottom actionbar is needed. On a timeout to get accurate heights
|
||||
if (this.bookContentElemRef == null) {
|
||||
setTimeout(() => this.updateLayoutMode(this.layoutMode), 10);
|
||||
setTimeout(() => this.updateLayoutMode(this.layoutMode()), 10);
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
|
|
@ -1609,8 +1621,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
updateImmersiveMode(immersiveMode: boolean) {
|
||||
this.immersiveMode = immersiveMode;
|
||||
if (this.immersiveMode && !this.epubMenuService.isDrawerOpen()) {
|
||||
this.immersiveMode.set(immersiveMode);
|
||||
if (immersiveMode && !this.epubMenuService.isDrawerOpen()) {
|
||||
this.actionBarVisible = false;
|
||||
this.updateReadingSectionHeight();
|
||||
}
|
||||
|
|
@ -1623,8 +1635,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
const elem = this.readingSectionElemRef;
|
||||
setTimeout(() => {
|
||||
if (renderer === undefined || elem === undefined) return;
|
||||
if (this.immersiveMode) {
|
||||
} else {
|
||||
if (!this.immersiveMode()) {
|
||||
renderer.setStyle(elem.nativeElement, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important);
|
||||
}
|
||||
});
|
||||
|
|
@ -1733,7 +1744,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
const targetElement = (event.target as Element);
|
||||
const mouseOffset = 5;
|
||||
|
||||
if (!this.immersiveMode) return;
|
||||
if (!this.immersiveMode()) return;
|
||||
if (targetElement.getAttribute('onclick') !== null || targetElement.getAttribute('href') !== null || targetElement.getAttribute('role') !== null || targetElement.getAttribute('kavita-part') != null) {
|
||||
// Don't do anything, it's actionable
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -271,16 +271,7 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
}
|
||||
|
||||
createNewProfileFromImplicit() {
|
||||
if (this.readingProfile.kind !== ReadingProfileKind.Implicit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.readerSettingsService.promoteProfile().subscribe(newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.parentReadingProfile = newProfile;
|
||||
this.cdRef.markForCheck();
|
||||
this.toastr.success(translate("manga-reader.reading-profile-promoted"));
|
||||
});
|
||||
this.readerSettingsService.createNewProfileFromImplicit();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue