diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index 557490219..3cc9f5285 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -826,6 +826,37 @@ public class ReaderController : BaseApiController return _readerService.GetTimeEstimate(0, pagesLeft, false); } + + /// + /// For the current user, returns an estimate on how long it would take to finish reading the chapter. + /// + /// For Epubs, this does not check words inside a chapter due to overhead so may not work in all cases. + /// + /// + /// + [HttpGet("time-left-for-chapter")] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["seriesId", "chapterId"])] + public async Task> 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); + } + /// /// Returns the annotations for the given chapter /// diff --git a/UI/Web/src/app/_models/common/i-has-reading-time.ts b/UI/Web/src/app/_models/common/i-has-reading-time.ts index 41753d1fd..3c6a25dd3 100644 --- a/UI/Web/src/app/_models/common/i-has-reading-time.ts +++ b/UI/Web/src/app/_models/common/i-has-reading-time.ts @@ -4,5 +4,4 @@ export interface IHasReadingTime { avgHoursToRead: number; pages: number; wordCount: number; - } diff --git a/UI/Web/src/app/_services/epub-reader-menu.service.ts b/UI/Web/src/app/_services/epub-reader-menu.service.ts index 7a6bd389f..396326966 100644 --- a/UI/Web/src/app/_services/epub-reader-menu.service.ts +++ b/UI/Web/src/app/_services/epub-reader-menu.service.ts @@ -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()); diff --git a/UI/Web/src/app/_services/epub-reader-settings.service.ts b/UI/Web/src/app/_services/epub-reader-settings.service.ts index 6d20f70fa..6a51417cb 100644 --- a/UI/Web/src/app/_services/epub-reader-settings.service.ts +++ b/UI/Web/src/app/_services/epub-reader-settings.service.ts @@ -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(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 { 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")); + }); + } } diff --git a/UI/Web/src/app/_services/reader.service.ts b/UI/Web/src/app/_services/reader.service.ts index bdbdccf47..04f659ef4 100644 --- a/UI/Web/src/app/_services/reader.service.ts +++ b/UI/Web/src/app/_services/reader.service.ts @@ -224,6 +224,10 @@ export class ReaderService { return this.httpClient.get(this.baseUrl + 'reader/time-left?seriesId=' + seriesId); } + getTimeLeftForChapter(seriesId: number, chapterId: number) { + return this.httpClient.get(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 */ diff --git a/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.html b/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.html index 76ce2e0e6..829fe9ad0 100644 --- a/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.html +++ b/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.html @@ -13,14 +13,6 @@ } diff --git a/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.ts b/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.ts index 50cc0a873..ab35c8317 100644 --- a/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.ts +++ b/UI/Web/src/app/book-reader/_components/_drawers/epub-setting-drawer/epub-setting-drawer.component.ts @@ -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(); readingProfile = model(); - updated = new EventEmitter(); +// updated = new EventEmitter(); 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(); diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html index 384ab74c6..24ef7e7d2 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html @@ -1,4 +1,4 @@ -
@@ -17,35 +17,35 @@ }
-
+
@if (clickToPaginate && !hidePagination) {
}
@if (page !== undefined) { -
- @if ((scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)) { + @if ((scrollbarNeeded || layoutMode() !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode() === BookPageLayoutMode.Default)) {
+ [ngClass]="{'bottom-bar': layoutMode() !== BookPageLayoutMode.Default}">
} @@ -55,7 +55,7 @@ - @if (!immersiveMode || epubMenuService.isDrawerOpen() || actionBarVisible) { + @if (!immersiveMode() || epubMenuService.isDrawerOpen() || actionBarVisible) {
} @else { - 20% complete, 3 hrs to go + @let timeLeft = readingTimeLeft(); + + 20% complete, + @if (timeLeft) { + + + {{timeLeft | readTimeLeft }} + + } + }
diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts index b9b85f23f..4e26b8e7d 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts @@ -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(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(true); + readingTimeLeft = model(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.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; diff --git a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts index 448f7f0a2..cb20636c8 100644 --- a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts +++ b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.ts @@ -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(); }