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:
Joseph Milazzo 2025-07-05 10:30:07 -05:00
parent 9474e8264a
commit d114949446
10 changed files with 186 additions and 140 deletions

View file

@ -826,6 +826,37 @@ public class ReaderController : BaseApiController
return _readerService.GetTimeEstimate(0, pagesLeft, false); 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> /// <summary>
/// Returns the annotations for the given chapter /// Returns the annotations for the given chapter
/// </summary> /// </summary>

View file

@ -4,5 +4,4 @@ export interface IHasReadingTime {
avgHoursToRead: number; avgHoursToRead: number;
pages: number; pages: number;
wordCount: number; wordCount: number;
} }

View file

@ -100,13 +100,13 @@ 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) => { // ref.componentInstance.updated.subscribe((res: ReaderSettingUpdate) => {
// Check if we are on mobile to collapse the menu // // Check if we are on mobile to collapse the menu
if (this.utilityService.activeUserBreakpoint() <= UserBreakpoint.Mobile) { // if (this.utilityService.activeUserBreakpoint() <= UserBreakpoint.Mobile) {
this.closeAll(); // this.closeAll();
} // }
callbackFn(res); // callbackFn(res);
}); // });
ref.closed.subscribe(() => this.setDrawerClosed()); ref.closed.subscribe(() => this.setDrawerClosed());
ref.dismissed.subscribe(() => this.setDrawerClosed()); ref.dismissed.subscribe(() => this.setDrawerClosed());

View file

@ -13,6 +13,8 @@ import {ReadingProfileService} from "./reading-profile.service";
import {debounceTime, skip, tap} from "rxjs/operators"; import {debounceTime, skip, tap} from "rxjs/operators";
import {BookTheme} from "../_models/preferences/book-theme"; import {BookTheme} from "../_models/preferences/book-theme";
import {DOCUMENT} from "@angular/common"; import {DOCUMENT} from "@angular/common";
import {translate} from "@jsverse/transloco";
import {ToastrService} from "ngx-toastr";
export interface ReaderSettingUpdate { export interface ReaderSettingUpdate {
setting: 'pageStyle' | 'clickToPaginate' | 'fullscreen' | 'writingStyle' | 'layoutMode' | 'readingDirection' | 'immersiveMode' | 'theme'; setting: 'pageStyle' | 'clickToPaginate' | 'fullscreen' | 'writingStyle' | 'layoutMode' | 'readingDirection' | 'immersiveMode' | 'theme';
@ -28,6 +30,7 @@ export class EpubReaderSettingsService {
private readonly bookService = inject(BookService); private readonly bookService = inject(BookService);
private readonly themeService = inject(ThemeService); private readonly themeService = inject(ThemeService);
private readonly readingProfileService = inject(ReadingProfileService); private readonly readingProfileService = inject(ReadingProfileService);
private readonly toastr = inject(ToastrService);
private readonly document = inject(DOCUMENT); private readonly document = inject(DOCUMENT);
private pageStylesSubject = new BehaviorSubject<PageStyle>(this.getDefaultPageStyles()); private pageStylesSubject = new BehaviorSubject<PageStyle>(this.getDefaultPageStyles());
@ -46,6 +49,7 @@ export class EpubReaderSettingsService {
// Form and data // Form and data
private settingsForm: FormGroup = new FormGroup({}); private settingsForm: FormGroup = new FormGroup({});
private currentReadingProfile: ReadingProfile | null = null; private currentReadingProfile: ReadingProfile | null = null;
private parentReadingProfile: ReadingProfile | null = null;
private currentSeriesId: number | null = null; private currentSeriesId: number | null = null;
private fontFamilies: FontFamily[] = this.bookService.getFontFamilies(); private fontFamilies: FontFamily[] = this.bookService.getFontFamilies();
@ -69,13 +73,15 @@ export class EpubReaderSettingsService {
async initialize(seriesId: number, readingProfile: ReadingProfile): Promise<void> { async initialize(seriesId: number, readingProfile: ReadingProfile): Promise<void> {
this.currentSeriesId = seriesId; this.currentSeriesId = seriesId;
this.currentReadingProfile = readingProfile; this.currentReadingProfile = readingProfile;
console.log('init, reading profile: ', readingProfile);
this.readingProfileSubject.next(readingProfile); this.readingProfileSubject.next(readingProfile);
// Load parent profile if needed // Load parent profile if needed
if (readingProfile.kind === ReadingProfileKind.Implicit) { if (readingProfile.kind === ReadingProfileKind.Implicit) {
try { try {
// const parent = await this.readingProfileService.getForSeries(seriesId, true).toPromise(); const parent = await this.readingProfileService.getForSeries(seriesId, true).toPromise();
// Keep the implicit profile but use parent as reference this.parentReadingProfile = parent || null;
// Keep the implicit profile but use parent as reference (TODO: Validate the code)
} catch (error) { } catch (error) {
console.error('Failed to load parent reading profile:', error); console.error('Failed to load parent reading profile:', error);
} }
@ -83,7 +89,7 @@ export class EpubReaderSettingsService {
// Setup defaults and form // Setup defaults and form
this.setupDefaultSettings(); this.setupDefaultSettings();
this.setupSettingsForm();
// Set initial theme // Set initial theme
const themeName = readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme; const themeName = readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme;
@ -227,6 +233,8 @@ export class EpubReaderSettingsService {
profile.bookReaderWritingStyle = WritingStyle.Horizontal; profile.bookReaderWritingStyle = WritingStyle.Horizontal;
} }
this.setupSettingsForm();
// Update internal state // Update internal state
this.readingDirectionSubject.next(profile.bookReaderReadingDirection); this.readingDirectionSubject.next(profile.bookReaderReadingDirection);
this.writingStyleSubject.next(profile.bookReaderWritingStyle); this.writingStyleSubject.next(profile.bookReaderWritingStyle);
@ -418,6 +426,8 @@ export class EpubReaderSettingsService {
data.bookReaderThemeName = activeTheme.name; data.bookReaderThemeName = activeTheme.name;
} }
console.log('packed reading profile:', data);
return 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"));
});
}
} }

View file

@ -224,6 +224,10 @@ export class ReaderService {
return this.httpClient.get<HourEstimateRange>(this.baseUrl + 'reader/time-left?seriesId=' + seriesId); 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 * Captures current body color and forces background color to be black. Call @see resetOverrideStyles() on destroy of component to revert changes
*/ */

View file

@ -13,14 +13,6 @@
<app-reader-settings <app-reader-settings
[seriesId]="sId" [seriesId]="sId"
[readingProfile]="rp" [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> ></app-reader-settings>
} }
</div> </div>

View file

@ -1,21 +1,8 @@
import { import {ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, inject, model} from '@angular/core';
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
effect,
EventEmitter,
inject,
model
} from '@angular/core';
import {NgbActiveOffcanvas} from "@ng-bootstrap/ng-bootstrap"; 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 {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 {TranslocoDirective} from "@jsverse/transloco";
import {ReaderSettingUpdate} from "../../../../_services/epub-reader-settings.service";
@Component({ @Component({
selector: 'app-epub-setting-drawer', selector: 'app-epub-setting-drawer',
@ -35,7 +22,7 @@ export class EpubSettingDrawerComponent {
seriesId = model<number>(); seriesId = model<number>();
readingProfile = model<ReadingProfile>(); readingProfile = model<ReadingProfile>();
updated = new EventEmitter<ReaderSettingUpdate>(); // updated = new EventEmitter<ReaderSettingUpdate>();
constructor() { constructor() {
@ -49,46 +36,46 @@ export class EpubSettingDrawerComponent {
}); });
} }
//
updateColorTheme(theme: BookTheme) { // updateColorTheme(theme: BookTheme) {
const evt = {setting: 'theme', object: theme} as ReaderSettingUpdate; // const evt = {setting: 'theme', object: theme} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
//
updateReaderStyles(pageStyles: PageStyle) { // updateReaderStyles(pageStyles: PageStyle) {
const evt = {setting: 'pageStyle', object: pageStyles} as ReaderSettingUpdate; // const evt = {setting: 'pageStyle', object: pageStyles} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
//
showPaginationOverlay(clickToPaginate: boolean) { // showPaginationOverlay(clickToPaginate: boolean) {
const evt = {setting: 'clickToPaginate', object: clickToPaginate} as ReaderSettingUpdate; // const evt = {setting: 'clickToPaginate', object: clickToPaginate} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
//
toggleFullscreen() { // toggleFullscreen() {
const evt = {setting: 'fullscreen', object: null} as ReaderSettingUpdate; // const evt = {setting: 'fullscreen', object: null} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
//
updateWritingStyle(writingStyle: WritingStyle) { // updateWritingStyle(writingStyle: WritingStyle) {
const evt = {setting: 'writingStyle', object: writingStyle} as ReaderSettingUpdate; // const evt = {setting: 'writingStyle', object: writingStyle} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
//
updateLayoutMode(mode: BookPageLayoutMode) { // updateLayoutMode(mode: BookPageLayoutMode) {
const evt = {setting: 'layoutMode', object: mode} as ReaderSettingUpdate; // const evt = {setting: 'layoutMode', object: mode} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
//
updateReadingDirection(readingDirection: ReadingDirection) { // updateReadingDirection(readingDirection: ReadingDirection) {
const evt = {setting: 'readingDirection', object: readingDirection} as ReaderSettingUpdate; // const evt = {setting: 'readingDirection', object: readingDirection} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
//
updateImmersiveMode(immersiveMode: boolean) { // updateImmersiveMode(immersiveMode: boolean) {
const evt = {setting: 'immersiveMode', object: immersiveMode} as ReaderSettingUpdate; // const evt = {setting: 'immersiveMode', object: immersiveMode} as ReaderSettingUpdate;
this.updated.emit(evt); // this.updated.emit(evt);
} // }
close() { close() {
this.activeOffcanvas.close(); this.activeOffcanvas.close();

View file

@ -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> tabindex="0" #reader>
<ng-container *transloco="let t; prefix: 'book-reader'"> <ng-container *transloco="let t; prefix: 'book-reader'">
<div class="fixed-top" #stickyTop> <div class="fixed-top" #stickyTop>
@ -17,35 +17,35 @@
} }
</div> </div>
<div #readingSection class="reading-section {{layoutMode | columnLayoutClass}} {{writingStyle | writingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}" <div #readingSection class="reading-section {{layoutMode() | columnLayoutClass}} {{writingStyle | writingStyleClass}}" [ngStyle]="{'width': PageWidthForPagination}"
[ngClass]="{'immersive' : immersiveMode || !actionBarVisible}" [@isLoading]="isLoading" (click)="handleReaderClick($event)"> [ngClass]="{'immersive' : immersiveMode() || !actionBarVisible}" [@isLoading]="isLoading" (click)="handleReaderClick($event)">
@if (clickToPaginate && !hidePagination) { @if (clickToPaginate && !hidePagination) {
<div class="left {{clickOverlayClass('left')}} no-observe" <div class="left {{clickOverlayClass('left')}} no-observe"
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
[ngClass]="{'immersive' : immersiveMode}" [ngClass]="{'immersive' : immersiveMode()}"
tabindex="-1" tabindex="-1"
[ngStyle]="{height: PageHeightForPagination}"></div> [ngStyle]="{height: PageHeightForPagination}"></div>
<div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe" <div class="{{scrollbarNeeded ? 'right-with-scrollbar' : 'right'}} {{clickOverlayClass('right')}} no-observe"
(click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
[ngClass]="{'immersive' : immersiveMode}" [ngClass]="{'immersive' : immersiveMode()}"
tabindex="-1" tabindex="-1"
[ngStyle]="{height: PageHeightForPagination}"></div> [ngStyle]="{height: PageHeightForPagination}"></div>
} }
<div #bookContainer class="book-container {{writingStyle | writingStyleClass}}" <div #bookContainer class="book-container {{writingStyle | writingStyleClass}}"
[ngClass]="{'immersive' : immersiveMode}" [ngClass]="{'immersive' : immersiveMode()}"
(mousedown)="mouseDown($event)" > (mousedown)="mouseDown($event)" >
@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>
@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();" <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> <ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
</div> </div>
} }
@ -55,7 +55,7 @@
<ng-template #topActionBar> <ng-template #topActionBar>
@if (!immersiveMode || epubMenuService.isDrawerOpen() || actionBarVisible) { @if (!immersiveMode() || epubMenuService.isDrawerOpen() || actionBarVisible) {
<div class="action-bar d-flex align-items-center px-2"> <div class="action-bar d-flex align-items-center px-2">
<!-- Left: Drawer toggle --> <!-- Left: Drawer toggle -->
<button class="btn btn-secondary me-2" (click)="toggleDrawer()"> <button class="btn btn-secondary me-2" (click)="toggleDrawer()">
@ -105,7 +105,7 @@
<ng-template #actionBar let-isTop> <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"> <div class="action-bar row g-0 justify-content-between">
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" <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)" (click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
@ -127,7 +127,16 @@
<span class="visually-hidden">{{t('loading-book')}}</span> <span class="visually-hidden">{{t('loading-book')}}</span>
</div> </div>
} @else { } @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> </div>

View file

@ -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 {EpubReaderSettingsService, ReaderSettingUpdate} from "../../../_services/epub-reader-settings.service";
import {ColumnLayoutClassPipe} from "../../_pipes/column-layout-class.pipe"; import {ColumnLayoutClassPipe} from "../../_pipes/column-layout-class.pipe";
import {WritingStyleClassPipe} from "../../_pipes/writing-style-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 { interface HistoryPoint {
@ -100,7 +103,7 @@ const elementLevelStyles = ['line-height', 'font-family'];
]) ])
], ],
imports: [NgTemplateOutlet, NgStyle, NgClass, NgbTooltip, imports: [NgTemplateOutlet, NgStyle, NgClass, NgbTooltip,
BookLineOverlayComponent, TranslocoDirective, ColumnLayoutClassPipe, WritingStyleClassPipe] BookLineOverlayComponent, TranslocoDirective, ColumnLayoutClassPipe, WritingStyleClassPipe, ReadTimeLeftPipe]
}) })
export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { 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 router = inject(Router);
private readonly seriesService = inject(SeriesService); private readonly seriesService = inject(SeriesService);
private readonly readerService = inject(ReaderService); private readonly readerService = inject(ReaderService);
private readonly chapterService = inject(ChapterService);
private readonly renderer = inject(Renderer2); private readonly renderer = inject(Renderer2);
private readonly navService = inject(NavService); private readonly navService = inject(NavService);
private readonly toastr = inject(ToastrService); private readonly toastr = inject(ToastrService);
@ -191,7 +195,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
/** /**
* Book reader setting that hides the menuing system * Book reader setting that hides the menuing system
*/ */
immersiveMode: boolean = false; immersiveMode = model<boolean>(false);
/** /**
* If we are loading from backend * 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 * 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. * 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 * 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 * 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() { 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;
} }
return condition; return condition;
@ -369,7 +375,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;
} }
return condition; return condition;
@ -379,7 +385,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* Determines if we show >> or > * Determines if we show >> or >
*/ */
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;
} }
@ -392,7 +398,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
* 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;
} }
@ -404,7 +410,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
get ColumnWidth() { get ColumnWidth() {
const base = this.writingStyle === WritingStyle.Vertical ? this.windowHeight : this.windowWidth; const base = this.writingStyle === WritingStyle.Vertical ? this.windowHeight : this.windowWidth;
switch (this.layoutMode) { switch (this.layoutMode()) {
case BookPageLayoutMode.Default: case BookPageLayoutMode.Default:
return 'unset'; return 'unset';
case BookPageLayoutMode.Column1: case BookPageLayoutMode.Column1:
@ -417,7 +423,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
get ColumnHeight() { 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 // Take the height after page loads, subtract the top/bottom bar
const height = this.windowHeight - (this.topOffset * 2); const height = this.windowHeight - (this.topOffset * 2);
return height + 'px'; return height + 'px';
@ -426,7 +432,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
get VerticalBookContentWidth() { get VerticalBookContentWidth() {
if (this.layoutMode !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Horizontal ) { if (this.layoutMode() !== BookPageLayoutMode.Default && this.writingStyle !== WritingStyle.Horizontal ) {
const width = this.getVerticalPageWidth() const width = this.getVerticalPageWidth()
return width + 'px'; return width + 'px';
} }
@ -435,23 +441,23 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
get PageWidthForPagination() { 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 'unset';
} }
return '100%' return '100%'
} }
get PageHeightForPagination() { 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 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 * (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'; return (this.windowHeight) - (this.topOffset * 2) + 'px';
} }
@ -543,6 +549,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (!this.incognitoMode) { if (!this.incognitoMode) {
this.readerService.saveProgress(this.libraryId, this.seriesId, this.volumeId, this.chapterId, tempPageNum, this.lastSeenScrollPartPath).pipe(take(1)).subscribe(() => {/* No operation */}); 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 { ngOnDestroy(): void {
@ -718,7 +730,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.updateImageSizes(); this.updateImageSizes();
const resumeElement = this.getFirstVisibleElementXPath(); 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 this.scrollTo(resumeElement); // This works pretty well, but not perfect
} }
} }
@ -750,7 +762,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
onWheel(event: WheelEvent) { onWheel(event: WheelEvent) {
// This allows the user to scroll the page horizontally without holding shift // 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; return;
} }
if (event.deltaY !== 0) { if (event.deltaY !== 0) {
@ -966,7 +978,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
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()) {
case BookPageLayoutMode.Default: case BookPageLayoutMode.Default:
if (isVerticalWritingStyle) { if (isVerticalWritingStyle) {
maxHeight = `${height}px`; maxHeight = `${height}px`;
@ -995,7 +1007,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
updateSingleImagePageStyles() { 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-position', 'absolute');
this.document.documentElement.style.setProperty('--book-reader-content-top', '50%'); this.document.documentElement.style.setProperty('--book-reader-content-top', '50%');
this.document.documentElement.style.setProperty('--book-reader-content-left', '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 // Virtual Paging stuff
this.updateWidthAndHeightCalcs(); this.updateWidthAndHeightCalcs();
this.updateLayoutMode(this.layoutMode); this.updateLayoutMode(this.layoutMode());
this.addEmptyPageIfRequired(); this.addEmptyPageIfRequired();
// Find all the part ids and their top offset // Find all the part ids and their top offset
@ -1043,11 +1055,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.scrollTo(part); this.scrollTo(part);
} else if (scrollTop !== undefined && scrollTop !== 0) { } else if (scrollTop !== undefined && scrollTop !== 0) {
setTimeout(() => this.scrollService.scrollTo(scrollTop, this.reader.nativeElement)); 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)); setTimeout(()=> this.scrollService.scrollToX(this.bookContentElemRef.nativeElement.clientWidth, this.reader.nativeElement));
} else { } else {
if (this.layoutMode === BookPageLayoutMode.Default) { if (this.layoutMode() === BookPageLayoutMode.Default) {
setTimeout(() => this.scrollService.scrollTo(0, this.reader.nativeElement)); setTimeout(() => this.scrollService.scrollTo(0, this.reader.nativeElement));
} else if (this.writingStyle === WritingStyle.Vertical) { } else if (this.writingStyle === WritingStyle.Vertical) {
if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) { if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) {
@ -1112,7 +1124,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
private addEmptyPageIfRequired(): void { private addEmptyPageIfRequired(): void {
if (this.layoutMode !== BookPageLayoutMode.Column2 || this.isSingleImagePage) { if (this.layoutMode() !== BookPageLayoutMode.Column2 || this.isSingleImagePage) {
return; return;
} }
@ -1194,7 +1206,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.pagingDirection = PAGING_DIRECTION.BACKWARDS; this.pagingDirection = PAGING_DIRECTION.BACKWARDS;
// We need to handle virtual paging before we increment the actual page // 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(); const [currentVirtualPage, _, pageWidth] = this.getVirtualPage();
if (currentVirtualPage > 1) { if (currentVirtualPage > 1) {
@ -1229,7 +1241,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.pagingDirection = PAGING_DIRECTION.FORWARD; this.pagingDirection = PAGING_DIRECTION.FORWARD;
// We need to handle virtual paging before we increment the actual page // 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(); const [currentVirtualPage, totalVirtualPages, pageWidth] = this.getVirtualPage();
if (currentVirtualPage < totalVirtualPages) { if (currentVirtualPage < totalVirtualPages) {
@ -1401,7 +1413,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
// After layout shifts, we need to refocus the scroll bar // 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.updateWidthAndHeightCalcs();
this.scrollTo(resumeElement); // This works pretty well, but not perfect this.scrollTo(resumeElement); // This works pretty well, but not perfect
} }
@ -1415,7 +1427,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
// 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());
this.darkMode = theme.isDarkTheme; this.darkMode.set(theme.isDarkTheme);
this.cdRef.markForCheck();
const styleElem = this.renderer.createElement('style'); const styleElem = this.renderer.createElement('style');
styleElem.id = theme.selector; styleElem.id = theme.selector;
@ -1471,13 +1484,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (drawerIsOpen) { if (drawerIsOpen) {
this.epubMenuService.closeAll(); this.epubMenuService.closeAll();
} else { } 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.epubMenuService.openSettingsDrawer(this.chapterId, this.seriesId, this.readingProfile, (res => {
this.handleReaderSettingsUpdate(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.actionBarVisible = false;
} }
this.cdRef.markForCheck(); this.cdRef.markForCheck();
@ -1498,12 +1510,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
if (element === null) return; 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 windowWidth = window.innerWidth || document.documentElement.clientWidth;
const scrollLeft = element.getBoundingClientRect().left + window.scrollX - (windowWidth - element.getBoundingClientRect().width); const scrollLeft = element.getBoundingClientRect().left + window.scrollX - (windowWidth - element.getBoundingClientRect().width);
setTimeout(() => this.scrollService.scrollToX(scrollLeft, this.reader.nativeElement, 'smooth'), 10); 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; 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 // 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); setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), 10);
@ -1545,7 +1557,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
// HACK: This is a bug with how browsers change the background color for fullscreen mode // 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')); 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'); this.renderer.setStyle(this.reader.nativeElement, 'background', 'white');
} }
}); });
@ -1555,7 +1567,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
updateWritingStyle(writingStyle: WritingStyle) { updateWritingStyle(writingStyle: WritingStyle) {
this.writingStyle = writingStyle; this.writingStyle = 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;
setTimeout(() => { setTimeout(() => {
this.scrollTo(lastSelector); this.scrollTo(lastSelector);
@ -1572,11 +1584,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
updateLayoutMode(mode: BookPageLayoutMode) { updateLayoutMode(mode: BookPageLayoutMode) {
const layoutModeChanged = mode !== this.layoutMode; const layoutModeChanged = mode !== this.layoutMode();
this.layoutMode = mode; this.layoutMode.set(mode);
this.cdRef.markForCheck(); this.cdRef.markForCheck();
console.log('layout mode changed to: ', this.layoutMode); console.log('layout mode changed to: ', this.layoutMode());
this.clearTimeout(this.updateImageSizeTimeout); this.clearTimeout(this.updateImageSizeTimeout);
this.updateImageSizeTimeout = setTimeout( () => { 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 // 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.updateLayoutMode(this.layoutMode()), 10);
return; return;
} }
setTimeout(() => { setTimeout(() => {
@ -1609,8 +1621,8 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
updateImmersiveMode(immersiveMode: boolean) { updateImmersiveMode(immersiveMode: boolean) {
this.immersiveMode = immersiveMode; this.immersiveMode.set(immersiveMode);
if (this.immersiveMode && !this.epubMenuService.isDrawerOpen()) { if (immersiveMode && !this.epubMenuService.isDrawerOpen()) {
this.actionBarVisible = false; this.actionBarVisible = false;
this.updateReadingSectionHeight(); this.updateReadingSectionHeight();
} }
@ -1623,8 +1635,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
const elem = this.readingSectionElemRef; const elem = this.readingSectionElemRef;
setTimeout(() => { setTimeout(() => {
if (renderer === undefined || elem === undefined) return; if (renderer === undefined || elem === undefined) return;
if (this.immersiveMode) { if (!this.immersiveMode()) {
} else {
renderer.setStyle(elem.nativeElement, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important); 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 targetElement = (event.target as Element);
const mouseOffset = 5; 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) { 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 // Don't do anything, it's actionable
return; return;

View file

@ -271,16 +271,7 @@ export class ReaderSettingsComponent implements OnInit {
} }
createNewProfileFromImplicit() { createNewProfileFromImplicit() {
if (this.readingProfile.kind !== ReadingProfileKind.Implicit) { this.readerSettingsService.createNewProfileFromImplicit();
return;
}
this.readerSettingsService.promoteProfile().subscribe(newProfile => {
this.readingProfile = newProfile;
this.parentReadingProfile = newProfile;
this.cdRef.markForCheck();
this.toastr.success(translate("manga-reader.reading-profile-promoted"));
});
} }