Fixed the webtoon reader issue by moving the reading profile into a resolver. Refactored the book reader to align with this new method as well.

This commit is contained in:
Joseph Milazzo 2025-06-04 17:13:58 -05:00
parent b36f6c8f0b
commit 37cd94b07a
10 changed files with 415 additions and 250 deletions

View file

@ -0,0 +1,18 @@
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs';
import {ReadingProfileService} from "../_services/reading-profile.service";
@Injectable({
providedIn: 'root'
})
export class ReadingProfileResolver implements Resolve<any> {
constructor(private readingProfileService: ReadingProfileService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
// Extract seriesId from route params or parent route
const seriesId = route.params['seriesId'] || route.parent?.params['seriesId'];
return this.readingProfileService.getForSeries(seriesId);
}
}

View file

@ -1,10 +1,14 @@
import { Routes } from '@angular/router'; import {Routes} from '@angular/router';
import { BookReaderComponent } from '../book-reader/_components/book-reader/book-reader.component'; import {BookReaderComponent} from '../book-reader/_components/book-reader/book-reader.component';
import {ReadingProfileResolver} from "../_resolvers/reading-profile.resolver";
export const routes: Routes = [ export const routes: Routes = [
{ {
path: ':chapterId', path: ':chapterId',
component: BookReaderComponent, component: BookReaderComponent,
resolve: {
readingProfile: ReadingProfileResolver
}
} }
]; ];

View file

@ -1,15 +1,22 @@
import { Routes } from '@angular/router'; import {Routes} from '@angular/router';
import { MangaReaderComponent } from '../manga-reader/_components/manga-reader/manga-reader.component'; import {MangaReaderComponent} from '../manga-reader/_components/manga-reader/manga-reader.component';
import {ReadingProfileResolver} from "../_resolvers/reading-profile.resolver";
export const routes: Routes = [ export const routes: Routes = [
{ {
path: ':chapterId', path: ':chapterId',
component: MangaReaderComponent component: MangaReaderComponent,
resolve: {
readingProfile: ReadingProfileResolver
}
}, },
{ {
// This will allow the MangaReader to have a list to use for next/prev chapters rather than natural sort order // This will allow the MangaReader to have a list to use for next/prev chapters rather than natural sort order
path: ':chapterId/list/:listId', path: ':chapterId/list/:listId',
component: MangaReaderComponent component: MangaReaderComponent,
resolve: {
readingProfile: ReadingProfileResolver
}
} }
]; ];

View file

@ -1,9 +1,13 @@
import { Routes } from '@angular/router'; import {Routes} from '@angular/router';
import { PdfReaderComponent } from '../pdf-reader/_components/pdf-reader/pdf-reader.component'; import {PdfReaderComponent} from '../pdf-reader/_components/pdf-reader/pdf-reader.component';
import {ReadingProfileResolver} from "../_resolvers/reading-profile.resolver";
export const routes: Routes = [ export const routes: Routes = [
{ {
path: ':chapterId', path: ':chapterId',
component: PdfReaderComponent, component: PdfReaderComponent,
resolve: {
readingProfile: ReadingProfileResolver
}
} }
]; ];

View file

@ -61,6 +61,7 @@
<ng-template ngbNavContent> <ng-template ngbNavContent>
<app-reader-settings <app-reader-settings
[seriesId]="seriesId" [seriesId]="seriesId"
[readingProfile]="readingProfile"
(colorThemeUpdate)="updateColorTheme($event)" (colorThemeUpdate)="updateColorTheme($event)"
(styleUpdate)="updateReaderStyles($event)" (styleUpdate)="updateReaderStyles($event)"
(clickToPaginateChanged)="showPaginationOverlay($event)" (clickToPaginateChanged)="showPaginationOverlay($event)"

View file

@ -63,6 +63,7 @@ import {
PersonalToCEvent PersonalToCEvent
} from "../personal-table-of-contents/personal-table-of-contents.component"; } from "../personal-table-of-contents/personal-table-of-contents.component";
import {translate, TranslocoDirective} from "@jsverse/transloco"; import {translate, TranslocoDirective} from "@jsverse/transloco";
import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
enum TabID { enum TabID {
@ -147,6 +148,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
chapterId!: number; chapterId!: number;
chapter!: Chapter; chapter!: Chapter;
user!: User; user!: User;
readingProfile!: ReadingProfile;
/** /**
* Reading List id. Defaults to -1. * Reading List id. Defaults to -1.
@ -608,6 +610,16 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return; return;
} }
this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(data => {
this.readingProfile = data['readingProfile'];
if (this.readingProfile == null) {
this.router.navigateByUrl('/home');
return;
}
//this.setupReaderSettings(); // TODO: Implement this Amelia
this.cdRef.markForCheck();
});
this.libraryId = parseInt(libraryId, 10); this.libraryId = parseInt(libraryId, 10);
this.seriesId = parseInt(seriesId, 10); this.seriesId = parseInt(seriesId, 10);
@ -668,7 +680,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.chapters = results.chapters; this.chapters = results.chapters;
this.pageNum = results.progress.pageNum; this.pageNum = results.progress.pageNum;
this.cdRef.markForCheck(); this.cdRef.markForCheck();
if (results.progress.bookScrollId) this.lastSeenScrollPartPath = results.progress.bookScrollId;
if (results.progress.bookScrollId) {
this.lastSeenScrollPartPath = results.progress.bookScrollId;
}
this.continuousChaptersStack.push(this.chapterId); this.continuousChaptersStack.push(this.chapterId);

View file

@ -1,31 +1,41 @@
import { DOCUMENT, NgFor, NgTemplateOutlet, NgIf, NgClass, NgStyle, TitleCasePipe } from '@angular/common'; import {DOCUMENT, NgClass, NgFor, NgIf, NgStyle, NgTemplateOutlet, TitleCasePipe} from '@angular/common';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, DestroyRef, Component,
DestroyRef,
EventEmitter, EventEmitter,
inject, inject,
Inject, Input, Inject,
Input,
OnInit, OnInit,
Output Output
} from '@angular/core'; } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {skip, take} from 'rxjs'; import {skip, take} from 'rxjs';
import { BookPageLayoutMode } from 'src/app/_models/readers/book-page-layout-mode'; import {BookPageLayoutMode} from 'src/app/_models/readers/book-page-layout-mode';
import { BookTheme } from 'src/app/_models/preferences/book-theme'; import {BookTheme} from 'src/app/_models/preferences/book-theme';
import { ReadingDirection } from 'src/app/_models/preferences/reading-direction'; import {ReadingDirection} from 'src/app/_models/preferences/reading-direction';
import { WritingStyle } from 'src/app/_models/preferences/writing-style'; import {WritingStyle} from 'src/app/_models/preferences/writing-style';
import { ThemeProvider } from 'src/app/_models/preferences/site-theme'; import {ThemeProvider} from 'src/app/_models/preferences/site-theme';
import { User } from 'src/app/_models/user'; import {User} from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service'; import {AccountService} from 'src/app/_services/account.service';
import { ThemeService } from 'src/app/_services/theme.service'; import {ThemeService} from 'src/app/_services/theme.service';
import { FontFamily, BookService } from '../../_services/book.service'; import {BookService, FontFamily} from '../../_services/book.service';
import { BookBlackTheme } from '../../_models/book-black-theme'; import {BookBlackTheme} from '../../_models/book-black-theme';
import { BookDarkTheme } from '../../_models/book-dark-theme'; import {BookDarkTheme} from '../../_models/book-dark-theme';
import { BookWhiteTheme } from '../../_models/book-white-theme'; import {BookWhiteTheme} from '../../_models/book-white-theme';
import { BookPaperTheme } from '../../_models/book-paper-theme'; import {BookPaperTheme} from '../../_models/book-paper-theme';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import { NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionToggle, NgbAccordionButton, NgbCollapse, NgbAccordionCollapse, NgbAccordionBody, NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import {
NgbAccordionBody,
NgbAccordionButton,
NgbAccordionCollapse,
NgbAccordionDirective,
NgbAccordionHeader,
NgbAccordionItem,
NgbTooltip
} from '@ng-bootstrap/ng-bootstrap';
import {TranslocoDirective} from "@jsverse/transloco"; import {TranslocoDirective} from "@jsverse/transloco";
import {ReadingProfileService} from "../../../_services/reading-profile.service"; import {ReadingProfileService} from "../../../_services/reading-profile.service";
import {ReadingProfile} from "../../../_models/preferences/reading-profiles"; import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
@ -92,10 +102,13 @@ const mobileBreakpointMarginOverride = 700;
templateUrl: './reader-settings.component.html', templateUrl: './reader-settings.component.html',
styleUrls: ['./reader-settings.component.scss'], styleUrls: ['./reader-settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton, NgbAccordionCollapse, NgbAccordionBody, NgFor, NgbTooltip, NgTemplateOutlet, NgIf, NgClass, NgStyle, TitleCasePipe, TranslocoDirective] imports: [ReactiveFormsModule, NgbAccordionDirective, NgbAccordionItem, NgbAccordionHeader, NgbAccordionButton,
NgbAccordionCollapse, NgbAccordionBody, NgFor, NgbTooltip, NgTemplateOutlet, NgIf, NgClass, NgStyle,
TitleCasePipe, TranslocoDirective]
}) })
export class ReaderSettingsComponent implements OnInit { export class ReaderSettingsComponent implements OnInit {
@Input({required:true}) seriesId!: number; @Input({required:true}) seriesId!: number;
@Input({required:true}) readingProfile!: ReadingProfile;
/** /**
* Outputs when clickToPaginate is changed * Outputs when clickToPaginate is changed
*/ */
@ -129,8 +142,6 @@ export class ReaderSettingsComponent implements OnInit {
*/ */
@Output() immersiveMode: EventEmitter<boolean> = new EventEmitter(); @Output() immersiveMode: EventEmitter<boolean> = new EventEmitter();
readingProfile: ReadingProfile | null = null;
user!: User; user!: User;
/** /**
* List of all font families user can select from * List of all font families user can select from
@ -172,8 +183,6 @@ export class ReaderSettingsComponent implements OnInit {
return WritingStyle; return WritingStyle;
} }
constructor(private bookService: BookService, private accountService: AccountService, constructor(private bookService: BookService, private accountService: AccountService,
@Inject(DOCUMENT) private document: Document, private themeService: ThemeService, @Inject(DOCUMENT) private document: Document, private themeService: ThemeService,
private readonly cdRef: ChangeDetectorRef, private readingProfileService: ReadingProfileService) {} private readonly cdRef: ChangeDetectorRef, private readingProfileService: ReadingProfileService) {}
@ -184,44 +193,40 @@ export class ReaderSettingsComponent implements OnInit {
this.fontOptions = this.fontFamilies.map(f => f.title); this.fontOptions = this.fontFamilies.map(f => f.title);
this.cdRef.markForCheck(); this.cdRef.markForCheck();
this.readingProfileService.getForSeries(this.seriesId).subscribe(profile => { if (this.readingProfile.bookReaderFontFamily === undefined) {
this.readingProfile = profile; this.readingProfile.bookReaderFontFamily = 'default';
}
if (this.readingProfile.bookReaderFontSize === undefined || this.readingProfile.bookReaderFontSize < 50) {
this.readingProfile.bookReaderFontSize = 100;
}
if (this.readingProfile.bookReaderLineSpacing === undefined || this.readingProfile.bookReaderLineSpacing < 100) {
this.readingProfile.bookReaderLineSpacing = 100;
}
if (this.readingProfile.bookReaderMargin === undefined) {
this.readingProfile.bookReaderMargin = 0;
}
if (this.readingProfile.bookReaderReadingDirection === undefined) {
this.readingProfile.bookReaderReadingDirection = ReadingDirection.LeftToRight;
}
if (this.readingProfile.bookReaderWritingStyle === undefined) {
this.readingProfile.bookReaderWritingStyle = WritingStyle.Horizontal;
}
this.readingDirectionModel = this.readingProfile.bookReaderReadingDirection;
this.writingStyleModel = this.readingProfile.bookReaderWritingStyle;
if (this.readingProfile.bookReaderFontFamily === undefined) { this.setupSettings();
this.readingProfile.bookReaderFontFamily = 'default';
}
if (this.readingProfile.bookReaderFontSize === undefined || this.readingProfile.bookReaderFontSize < 50) {
this.readingProfile.bookReaderFontSize = 100;
}
if (this.readingProfile.bookReaderLineSpacing === undefined || this.readingProfile.bookReaderLineSpacing < 100) {
this.readingProfile.bookReaderLineSpacing = 100;
}
if (this.readingProfile.bookReaderMargin === undefined) {
this.readingProfile.bookReaderMargin = 0;
}
if (this.readingProfile.bookReaderReadingDirection === undefined) {
this.readingProfile.bookReaderReadingDirection = ReadingDirection.LeftToRight;
}
if (this.readingProfile.bookReaderWritingStyle === undefined) {
this.readingProfile.bookReaderWritingStyle = WritingStyle.Horizontal;
}
this.readingDirectionModel = this.readingProfile.bookReaderReadingDirection;
this.writingStyleModel = this.readingProfile.bookReaderWritingStyle;
this.setupSettings(); this.setTheme(this.readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme, false);
this.cdRef.markForCheck();
this.setTheme(this.readingProfile.bookReaderThemeName || this.themeService.defaultBookTheme, false); // Emit first time so book reader gets the setting
this.cdRef.markForCheck(); this.readingDirection.emit(this.readingDirectionModel);
this.bookReaderWritingStyle.emit(this.writingStyleModel);
this.clickToPaginateChanged.emit(this.readingProfile.bookReaderTapToPaginate);
this.layoutModeUpdate.emit(this.readingProfile.bookReaderLayoutMode);
this.immersiveMode.emit(this.readingProfile.bookReaderImmersiveMode);
// Emit first time so book reader gets the setting this.resetSettings();
this.readingDirection.emit(this.readingDirectionModel);
this.bookReaderWritingStyle.emit(this.writingStyleModel);
this.clickToPaginateChanged.emit(this.readingProfile.bookReaderTapToPaginate);
this.layoutModeUpdate.emit(this.readingProfile.bookReaderLayoutMode);
this.immersiveMode.emit(this.readingProfile.bookReaderImmersiveMode);
this.resetSettings();
})
this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) { if (user) {

View file

@ -53,96 +53,94 @@
<div class="reading-area" <div class="reading-area"
ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)" ngSwipe (swipeEnd)="onSwipeEnd($event)" (swipeMove)="onSwipeMove($event)"
[ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : '100dvh'}" #readingArea> [ngStyle]="{'background-color': backgroundColor, 'height': readerMode === ReaderMode.Webtoon ? 'inherit' : '100dvh'}" #readingArea>
@if (readingProfile !== null) { @if (readerMode !== ReaderMode.Webtoon) {
@if (readerMode !== ReaderMode.Webtoon) { <div appDblClick (dblclick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<div appDblClick (dblclick)="bookmarkPage($event)" (singleClick)="toggleMenu()"> <app-canvas-renderer
<app-canvas-renderer [readerSettings$]="readerSettings$"
[readerSettings$]="readerSettings$" [image$]="currentImage$"
[image$]="currentImage$" [bookmark$]="showBookmarkEffect$"
[bookmark$]="showBookmarkEffect$" [showClickOverlay$]="showClickOverlay$">
[showClickOverlay$]="showClickOverlay$"> </app-canvas-renderer>
</app-canvas-renderer> </div>
</div>
<!-- Pagination controls and screen hints--> <!-- Pagination controls and screen hints-->
<div class="pagination-area"> <div class="pagination-area">
<div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, KeyDirection.Left)" <div class="{{readerMode === ReaderMode.LeftRight ? 'left' : 'top'}} {{clickOverlayClass('left')}}" (click)="handlePageChange($event, KeyDirection.Left)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), 'max-height': MaxHeight}"> [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), 'max-height': MaxHeight}">
@if (showClickOverlay) { @if (showClickOverlay) {
<div> <div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}" <i class="fa fa-angle-{{readingDirection === ReadingDirection.RightToLeft ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'left' : 'up'}}"
[title]="t('prev-page-tooltip')" aria-hidden="true"></i> [title]="t('prev-page-tooltip')" aria-hidden="true"></i>
</div> </div>
} }
</div> </div>
<div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, KeyDirection.Right)" <div class="{{readerMode === ReaderMode.LeftRight ? 'right' : 'bottom'}} {{clickOverlayClass('right')}}" (click)="handlePageChange($event, KeyDirection.Right)"
[ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'), [ngStyle]="{'height': (readerMode === ReaderMode.LeftRight ? MaxHeight: '25%'),
'left': 'inherit', 'left': 'inherit',
'right': RightPaginationOffset + 'px', 'right': RightPaginationOffset + 'px',
'max-height': MaxHeight}"> 'max-height': MaxHeight}">
@if (showClickOverlay) { @if (showClickOverlay) {
<div> <div>
<i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}" <i class="fa fa-angle-{{readingDirection === ReadingDirection.LeftToRight ? 'double-' : ''}}{{readerMode === ReaderMode.LeftRight ? 'right' : 'down'}}"
[title]="t('next-page-tooltip')" aria-hidden="true"></i> [title]="t('next-page-tooltip')" aria-hidden="true"></i>
</div> </div>
} }
</div>
</div> </div>
</div>
<div appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()"> <div appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-single-renderer [image$]="currentImage$" <app-single-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$" [readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$" [bookmark$]="showBookmarkEffect$"
[pageNum$]="pageNum$" [pageNum$]="pageNum$"
[showClickOverlay$]="showClickOverlay$"> [showClickOverlay$]="showClickOverlay$">
</app-single-renderer> </app-single-renderer>
<app-double-renderer [image$]="currentImage$" <app-double-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$" [readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$" [bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$" [showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$" [pageNum$]="pageNum$"
[getPage]="getPageFn"> [getPage]="getPageFn">
</app-double-renderer> </app-double-renderer>
<app-double-reverse-renderer [image$]="currentImage$" <app-double-reverse-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$" [readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$" [bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$" [showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$" [pageNum$]="pageNum$"
[getPage]="getPageFn"> [getPage]="getPageFn">
</app-double-reverse-renderer> </app-double-reverse-renderer>
<app-double-no-cover-renderer [image$]="currentImage$" <app-double-no-cover-renderer [image$]="currentImage$"
[readerSettings$]="readerSettings$" [readerSettings$]="readerSettings$"
[bookmark$]="showBookmarkEffect$" [bookmark$]="showBookmarkEffect$"
[showClickOverlay$]="showClickOverlay$" [showClickOverlay$]="showClickOverlay$"
[pageNum$]="pageNum$" [pageNum$]="pageNum$"
[getPage]="getPageFn"> [getPage]="getPageFn">
</app-double-no-cover-renderer> </app-double-no-cover-renderer>
</div>
} @else {
@if (!isLoading && !inSetup) {
<div class="webtoon-images" appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-infinite-scroller [pageNum]="pageNum"
[bufferPages]="5"
[goToPage]="goToPageEvent"
(pageNumberChange)="handleWebtoonPageChange($event)"
[totalPages]="maxPages"
[urlProvider]="getPageUrl"
(loadNextChapter)="loadNextChapter()"
(loadPrevChapter)="loadPrevChapter()"
[bookmarkPage]="showBookmarkEffectEvent"
[fullscreenToggled]="fullscreenEvent"
[readerSettings$]="readerSettings$">
</app-infinite-scroller>
</div> </div>
} @else {
@if (!isLoading && !inSetup) {
<div class="webtoon-images" appDblClick (doubleClick)="bookmarkPage($event)" (singleClick)="toggleMenu()">
<app-infinite-scroller [pageNum]="pageNum"
[bufferPages]="5"
[goToPage]="goToPageEvent"
(pageNumberChange)="handleWebtoonPageChange($event)"
[totalPages]="maxPages"
[urlProvider]="getPageUrl"
(loadNextChapter)="loadNextChapter()"
(loadPrevChapter)="loadPrevChapter()"
[bookmarkPage]="showBookmarkEffectEvent"
[fullscreenToggled]="fullscreenEvent"
[readerSettings$]="readerSettings$">
</app-infinite-scroller>
</div>
}
} }
} }
</div> </div>
@if (menuOpen && readingProfile !== null) { @if (menuOpen) {
<div class="fixed-bottom overlay" [@slideFromBottom]="menuOpen"> <div class="fixed-bottom overlay" [@slideFromBottom]="menuOpen">
@if (pageOptions !== undefined && pageOptions.ceil !== undefined) { @if (pageOptions !== undefined && pageOptions.ceil !== undefined) {
<div class="mb-3"> <div class="mb-3">

View file

@ -197,7 +197,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
totalSeriesPages = 0; totalSeriesPages = 0;
totalSeriesPagesRead = 0; totalSeriesPagesRead = 0;
user!: User; user!: User;
readingProfile: ReadingProfile | null = null; readingProfile!: ReadingProfile;
generalSettingsForm!: FormGroup; generalSettingsForm!: FormGroup;
readingDirection = ReadingDirection.LeftToRight; readingDirection = ReadingDirection.LeftToRight;
@ -484,6 +484,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
return; return;
} }
this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(data => {
this.readingProfile = data['readingProfile'];
if (this.readingProfile == null) {
this.router.navigateByUrl('/home');
return;
}
this.setupReaderSettings();
this.cdRef.markForCheck();
});
this.getPageFn = this.getPage.bind(this); this.getPageFn = this.getPage.bind(this);
this.libraryId = parseInt(libraryId, 10); this.libraryId = parseInt(libraryId, 10);
@ -500,113 +511,108 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
this.continuousChaptersStack.push(this.chapterId); this.continuousChaptersStack.push(this.chapterId);
forkJoin([ this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
this.accountService.currentUser$.pipe(take(1)),
this.readingProfileService.getForSeries(this.seriesId)
]).subscribe(([user, profile]) => {
if (!user) { if (!user) {
this.router.navigateByUrl('/login'); this.router.navigateByUrl('/login');
return; return;
} }
this.readingProfile = profile;
if (!this.readingProfile) return; // type hints
this.user = user; this.user = user;
this.hasBookmarkRights = this.accountService.hasBookmarkRole(user) || this.accountService.hasAdminRole(user); this.hasBookmarkRights = this.accountService.hasBookmarkRole(user) || this.accountService.hasAdminRole(user);
this.readingDirection = this.readingProfile.readingDirection; // this.readingDirection = this.readingProfile.readingDirection;
this.scalingOption = this.readingProfile.scalingOption; // this.scalingOption = this.readingProfile.scalingOption;
this.pageSplitOption = this.readingProfile.pageSplitOption; // this.pageSplitOption = this.readingProfile.pageSplitOption;
this.autoCloseMenu = this.readingProfile.autoCloseMenu; // this.autoCloseMenu = this.readingProfile.autoCloseMenu;
this.readerMode = this.readingProfile.readerMode; // this.readerMode = this.readingProfile.readerMode;
this.layoutMode = this.readingProfile.layoutMode || LayoutMode.Single; // this.layoutMode = this.readingProfile.layoutMode || LayoutMode.Single;
this.backgroundColor = this.readingProfile.backgroundColor || '#000000'; // this.backgroundColor = this.readingProfile.backgroundColor || '#000000';
this.readerService.setOverrideStyles(this.backgroundColor); // this.readerService.setOverrideStyles(this.backgroundColor);
//
// this.generalSettingsForm = this.formBuilder.nonNullable.group({
// autoCloseMenu: new FormControl(this.autoCloseMenu),
// pageSplitOption: new FormControl(this.pageSplitOption),
// fittingOption: new FormControl(this.mangaReaderService.translateScalingOption(this.scalingOption)),
// widthSlider: new FormControl(this.readingProfile.widthOverride ?? 'none'),
// layoutMode: new FormControl(this.layoutMode),
// darkness: new FormControl(100),
// emulateBook: new FormControl(this.readingProfile.emulateBook),
// swipeToPaginate: new FormControl(this.readingProfile.swipeToPaginate)
// });
//
// this.readerModeSubject.next(this.readerMode);
// this.pagingDirectionSubject.next(this.pagingDirection);
this.generalSettingsForm = this.formBuilder.nonNullable.group({ // // We need a mergeMap when page changes
autoCloseMenu: new FormControl(this.autoCloseMenu), // this.readerSettings$ = merge(this.generalSettingsForm.valueChanges, this.pagingDirection$, this.readerMode$).pipe(
pageSplitOption: new FormControl(this.pageSplitOption), // map(_ => this.createReaderSettingsUpdate()),
fittingOption: new FormControl(this.mangaReaderService.translateScalingOption(this.scalingOption)), // takeUntilDestroyed(this.destroyRef),
widthSlider: new FormControl(this.readingProfile.widthOverride ?? 'none'), // );
layoutMode: new FormControl(this.layoutMode), //
darkness: new FormControl(100), // this.updateForm();
emulateBook: new FormControl(this.readingProfile.emulateBook), //
swipeToPaginate: new FormControl(this.readingProfile.swipeToPaginate) // this.pagingDirection$.pipe(
}); // distinctUntilChanged(),
// tap(dir => {
this.readerModeSubject.next(this.readerMode); // this.pagingDirection = dir;
this.pagingDirectionSubject.next(this.pagingDirection); // this.cdRef.markForCheck();
// }),
// We need a mergeMap when page changes // takeUntilDestroyed(this.destroyRef)
this.readerSettings$ = merge(this.generalSettingsForm.valueChanges, this.pagingDirection$, this.readerMode$).pipe( // ).subscribe(() => {});
map(_ => this.createReaderSettingsUpdate()), //
takeUntilDestroyed(this.destroyRef), // this.readerMode$.pipe(
); // distinctUntilChanged(),
// tap(mode => {
this.updateForm(); // this.readerMode = mode;
// this.disableDoubleRendererIfScreenTooSmall();
this.pagingDirection$.pipe( // this.cdRef.markForCheck();
distinctUntilChanged(), // }),
tap(dir => { // takeUntilDestroyed(this.destroyRef)
this.pagingDirection = dir; // ).subscribe(() => {});
this.cdRef.markForCheck(); //
}), // this.setupWidthOverrideTriggers();
takeUntilDestroyed(this.destroyRef) //
).subscribe(() => {}); // this.generalSettingsForm.get('layoutMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
//
this.readerMode$.pipe( // const changeOccurred = parseInt(val, 10) !== this.layoutMode;
distinctUntilChanged(), // this.layoutMode = parseInt(val, 10);
tap(mode => { //
this.readerMode = mode; // if (this.layoutMode === LayoutMode.Single) {
this.disableDoubleRendererIfScreenTooSmall(); // this.generalSettingsForm.get('pageSplitOption')?.setValue(this.readingProfile!.pageSplitOption);
this.cdRef.markForCheck(); // this.generalSettingsForm.get('pageSplitOption')?.enable();
}), // this.generalSettingsForm.get('widthSlider')?.enable();
takeUntilDestroyed(this.destroyRef) // this.generalSettingsForm.get('fittingOption')?.enable();
).subscribe(() => {}); // this.generalSettingsForm.get('emulateBook')?.enable();
// } else {
this.setupWidthOverrideTriggers(); // this.generalSettingsForm.get('pageSplitOption')?.setValue(PageSplitOption.NoSplit);
// this.generalSettingsForm.get('pageSplitOption')?.disable();
this.generalSettingsForm.get('layoutMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => { // this.generalSettingsForm.get('widthSlider')?.disable();
// this.generalSettingsForm.get('fittingOption')?.setValue(this.mangaReaderService.translateScalingOption(ScalingOption.FitToHeight));
const changeOccurred = parseInt(val, 10) !== this.layoutMode; // this.generalSettingsForm.get('fittingOption')?.disable();
this.layoutMode = parseInt(val, 10); // this.generalSettingsForm.get('emulateBook')?.enable();
// }
if (this.layoutMode === LayoutMode.Single) { // this.cdRef.markForCheck();
this.generalSettingsForm.get('pageSplitOption')?.setValue(this.readingProfile!.pageSplitOption); //
this.generalSettingsForm.get('pageSplitOption')?.enable(); // // Re-render the current page when we switch layouts
this.generalSettingsForm.get('widthSlider')?.enable(); // if (changeOccurred) {
this.generalSettingsForm.get('fittingOption')?.enable(); // this.setPageNum(this.adjustPagesForDoubleRenderer(this.pageNum));
this.generalSettingsForm.get('emulateBook')?.enable(); // this.loadPage();
} else { // }
this.generalSettingsForm.get('pageSplitOption')?.setValue(PageSplitOption.NoSplit); // });
this.generalSettingsForm.get('pageSplitOption')?.disable(); //
this.generalSettingsForm.get('widthSlider')?.disable(); // this.generalSettingsForm.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.generalSettingsForm.get('fittingOption')?.setValue(this.mangaReaderService.translateScalingOption(ScalingOption.FitToHeight)); // this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value;
this.generalSettingsForm.get('fittingOption')?.disable(); // this.pageSplitOption = parseInt(this.generalSettingsForm.get('pageSplitOption')?.value, 10);
this.generalSettingsForm.get('emulateBook')?.enable(); //
} // const needsSplitting = this.mangaReaderService.isWidePage(this.readerService.imageUrlToPageNum(this.canvasImage.src));
this.cdRef.markForCheck(); // // If we need to split on a menu change, then we need to re-render.
// if (needsSplitting) {
// Re-render the current page when we switch layouts // // If we need to re-render, to ensure things layout properly, let's update paging direction & reset render
if (changeOccurred) { // this.pagingDirectionSubject.next(PAGING_DIRECTION.FORWARD);
this.setPageNum(this.adjustPagesForDoubleRenderer(this.pageNum)); // this.canvasRenderer.reset();
this.loadPage(); // this.loadPage();
} // }
}); // });
this.generalSettingsForm.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value;
this.pageSplitOption = parseInt(this.generalSettingsForm.get('pageSplitOption')?.value, 10);
const needsSplitting = this.mangaReaderService.isWidePage(this.readerService.imageUrlToPageNum(this.canvasImage.src));
// If we need to split on a menu change, then we need to re-render.
if (needsSplitting) {
// If we need to re-render, to ensure things layout properly, let's update paging direction & reset render
this.pagingDirectionSubject.next(PAGING_DIRECTION.FORWARD);
this.canvasRenderer.reset();
this.loadPage();
}
});
this.memberService.hasReadingProgress(this.libraryId).pipe(take(1)).subscribe(progress => { this.memberService.hasReadingProgress(this.libraryId).pipe(take(1)).subscribe(progress => {
if (!progress) { if (!progress) {
@ -724,6 +730,104 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
setupReaderSettings() {
this.readingDirection = this.readingProfile.readingDirection;
this.scalingOption = this.readingProfile.scalingOption;
this.pageSplitOption = this.readingProfile.pageSplitOption;
this.autoCloseMenu = this.readingProfile.autoCloseMenu;
this.readerMode = this.readingProfile.readerMode;
this.layoutMode = this.readingProfile.layoutMode || LayoutMode.Single;
this.backgroundColor = this.readingProfile.backgroundColor || '#000000';
this.readerService.setOverrideStyles(this.backgroundColor);
this.generalSettingsForm = this.formBuilder.nonNullable.group({
autoCloseMenu: new FormControl(this.autoCloseMenu),
pageSplitOption: new FormControl(this.pageSplitOption),
fittingOption: new FormControl(this.mangaReaderService.translateScalingOption(this.scalingOption)),
widthSlider: new FormControl(this.readingProfile.widthOverride ?? 'none'),
layoutMode: new FormControl(this.layoutMode),
darkness: new FormControl(100),
emulateBook: new FormControl(this.readingProfile.emulateBook),
swipeToPaginate: new FormControl(this.readingProfile.swipeToPaginate)
});
this.readerModeSubject.next(this.readerMode);
this.pagingDirectionSubject.next(this.pagingDirection);
// We need a mergeMap when page changes
this.readerSettings$ = merge(this.generalSettingsForm.valueChanges, this.pagingDirection$, this.readerMode$).pipe(
map(_ => this.createReaderSettingsUpdate()),
takeUntilDestroyed(this.destroyRef),
);
this.updateForm();
this.pagingDirection$.pipe(
distinctUntilChanged(),
tap(dir => {
this.pagingDirection = dir;
this.cdRef.markForCheck();
}),
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {});
this.readerMode$.pipe(
distinctUntilChanged(),
tap(mode => {
this.readerMode = mode;
this.disableDoubleRendererIfScreenTooSmall();
this.cdRef.markForCheck();
}),
takeUntilDestroyed(this.destroyRef)
).subscribe(() => {});
this.setupWidthOverrideTriggers();
this.generalSettingsForm.get('layoutMode')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(val => {
const changeOccurred = parseInt(val, 10) !== this.layoutMode;
this.layoutMode = parseInt(val, 10);
if (this.layoutMode === LayoutMode.Single) {
this.generalSettingsForm.get('pageSplitOption')?.setValue(this.readingProfile!.pageSplitOption);
this.generalSettingsForm.get('pageSplitOption')?.enable();
this.generalSettingsForm.get('widthSlider')?.enable();
this.generalSettingsForm.get('fittingOption')?.enable();
this.generalSettingsForm.get('emulateBook')?.enable();
} else {
this.generalSettingsForm.get('pageSplitOption')?.setValue(PageSplitOption.NoSplit);
this.generalSettingsForm.get('pageSplitOption')?.disable();
this.generalSettingsForm.get('widthSlider')?.disable();
this.generalSettingsForm.get('fittingOption')?.setValue(this.mangaReaderService.translateScalingOption(ScalingOption.FitToHeight));
this.generalSettingsForm.get('fittingOption')?.disable();
this.generalSettingsForm.get('emulateBook')?.enable();
}
this.cdRef.markForCheck();
// Re-render the current page when we switch layouts
if (changeOccurred) {
this.setPageNum(this.adjustPagesForDoubleRenderer(this.pageNum));
this.loadPage();
}
});
this.generalSettingsForm.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.autoCloseMenu = this.generalSettingsForm.get('autoCloseMenu')?.value;
this.pageSplitOption = parseInt(this.generalSettingsForm.get('pageSplitOption')?.value, 10);
const needsSplitting = this.mangaReaderService.isWidePage(this.readerService.imageUrlToPageNum(this.canvasImage.src));
// If we need to split on a menu change, then we need to re-render.
if (needsSplitting) {
// If we need to re-render, to ensure things layout properly, let's update paging direction & reset render
this.pagingDirectionSubject.next(PAGING_DIRECTION.FORWARD);
this.canvasRenderer.reset();
this.loadPage();
}
});
this.cdRef.markForCheck();
}
/** /**
* Width override is only valid under the following conditions: * Width override is only valid under the following conditions:
* Image Scaling is Width * Image Scaling is Width

View file

@ -14,7 +14,7 @@ import {
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {NgxExtendedPdfViewerModule, PageViewModeType, ProgressBarEvent, ScrollModeType} from 'ngx-extended-pdf-viewer'; import {NgxExtendedPdfViewerModule, PageViewModeType, ProgressBarEvent, ScrollModeType} from 'ngx-extended-pdf-viewer';
import {ToastrService} from 'ngx-toastr'; import {ToastrService} from 'ngx-toastr';
import {forkJoin, take} from 'rxjs'; import {take} from 'rxjs';
import {BookService} from 'src/app/book-reader/_services/book.service'; import {BookService} from 'src/app/book-reader/_services/book.service';
import {Breakpoint, KEY_CODES, UtilityService} from 'src/app/shared/_services/utility.service'; import {Breakpoint, KEY_CODES, UtilityService} from 'src/app/shared/_services/utility.service';
import {Chapter} from 'src/app/_models/chapter'; import {Chapter} from 'src/app/_models/chapter';
@ -36,6 +36,7 @@ import {PdfScrollModeTypePipe} from "../../_pipe/pdf-scroll-mode.pipe";
import {PdfSpreadTypePipe} from "../../_pipe/pdf-spread-mode.pipe"; import {PdfSpreadTypePipe} from "../../_pipe/pdf-spread-mode.pipe";
import {ReadingProfileService} from "../../../_services/reading-profile.service"; import {ReadingProfileService} from "../../../_services/reading-profile.service";
import {ReadingProfile} from "../../../_models/preferences/reading-profiles"; import {ReadingProfile} from "../../../_models/preferences/reading-profiles";
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
@Component({ @Component({
selector: 'app-pdf-reader', selector: 'app-pdf-reader',
@ -166,6 +167,16 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
this.chapterId = parseInt(chapterId, 10); this.chapterId = parseInt(chapterId, 10);
this.incognitoMode = this.route.snapshot.queryParamMap.get('incognitoMode') === 'true'; this.incognitoMode = this.route.snapshot.queryParamMap.get('incognitoMode') === 'true';
this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(data => {
this.readingProfile = data['readingProfile'];
if (this.readingProfile == null) {
this.router.navigateByUrl('/home');
return;
}
this.setupReaderSettings();
this.cdRef.markForCheck();
});
const readingListId = this.route.snapshot.queryParamMap.get('readingListId'); const readingListId = this.route.snapshot.queryParamMap.get('readingListId');
if (readingListId != null) { if (readingListId != null) {
@ -175,13 +186,9 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
this.cdRef.markForCheck(); this.cdRef.markForCheck();
forkJoin([ this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
this.accountService.currentUser$.pipe(take(1)),
this.readingProfileService.getForSeries(this.seriesId)
]).subscribe(([user, profile]) => {
if (user) { if (user) {
this.user = user; this.user = user;
this.readingProfile = profile;
this.init(); this.init();
} }
}); });
@ -242,12 +249,14 @@ export class PdfReaderComponent implements OnInit, OnDestroy {
} }
} }
init() { setupReaderSettings() {
this.pageLayoutMode = this.convertPdfLayoutMode(PdfLayoutMode.Multiple); this.pageLayoutMode = this.convertPdfLayoutMode(PdfLayoutMode.Multiple);
this.scrollMode = this.convertPdfScrollMode(this.readingProfile.pdfScrollMode || PdfScrollMode.Vertical); this.scrollMode = this.convertPdfScrollMode(this.readingProfile.pdfScrollMode || PdfScrollMode.Vertical);
this.spreadMode = this.convertPdfSpreadMode(this.readingProfile.pdfSpreadMode || PdfSpreadMode.None); this.spreadMode = this.convertPdfSpreadMode(this.readingProfile.pdfSpreadMode || PdfSpreadMode.None);
this.theme = this.convertPdfTheme(this.readingProfile.pdfTheme || PdfTheme.Dark); this.theme = this.convertPdfTheme(this.readingProfile.pdfTheme || PdfTheme.Dark);
}
init() {
this.backgroundColor = this.themeMap[this.theme].background; this.backgroundColor = this.themeMap[this.theme].background;
this.fontColor = this.themeMap[this.theme].font; // TODO: Move this to an observable or something this.fontColor = this.themeMap[this.theme].font; // TODO: Move this to an observable or something