New PDF Reader (#1324)

* Refactored all the code that opens the reader to use a unified function. Added new library and setup basic pdf reader route.

* Progress saving is implemented. Targeting ES6 now.

* Customized the toolbar to remove things we don't want, made the download button download with correct filename. Adjusted zoom setting to work well on first load regardless of device.

* Stream the pdf file to the UI rather than handling the download ourselves.

* Started implementing a custom toolbar.

* Fixed up the jump bar calculations

* Fixed filtering being broken

* Pushing up for Robbie to cleanup the toolbar layout

* Added an additional button. Working on logic while robbie takes styling

* Tried to fix the code for robbie

* Tweaks for fonts

* Added button for book mode, but doesn't seem to work after renderer is built

* Removed book mode

* Removed the old image caching code for pdfs as it's not needed with new reader

* Removed the interfaces to extract images from pdf.

* Fixed original pagination area not scaling correctly

* Integrated series remove events to library detail

* Cleaned up the getter naming convention

* Cleaned up some of the manga reader code to reduce cluter and improve re-use

* Implemented Japanese parser support for volume and chapters.

* Fixed a bug where resetting scroll in manga reader wasn't working

* Fixed a bug where word count grew on each scan.

* Removed unused variable

* Ensure we calculate word count on files with their own cache timestamp

* Adjusted size of reel headers

* Put some code in for moving on original image with keyboard, but it's not in use.

* Cleaned up the css for the pdf reader

* Cleaned up the code

* Tweaked the list item so we show scrollbar now when fully read
This commit is contained in:
Joseph Milazzo 2022-06-15 16:43:32 -05:00 committed by GitHub
parent 384fac68c4
commit 3ab3a10ae7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 2309 additions and 208 deletions

View file

@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { PdfReaderComponent } from './pdf-reader/pdf-reader.component';
import { PdfReaderRoutingModule } from './pdf-reader.router.module';
import { NgxExtendedPdfViewerModule } from 'ngx-extended-pdf-viewer';
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [
PdfReaderComponent
],
imports: [
CommonModule,
PdfReaderRoutingModule,
NgxExtendedPdfViewerModule,
NgbTooltipModule
]
})
export class PdfReaderModule { }

View file

@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PdfReaderComponent } from './pdf-reader/pdf-reader.component';
const routes: Routes = [
{
path: ':chapterId',
component: PdfReaderComponent,
}
];
@NgModule({
imports: [RouterModule.forChild(routes), ],
exports: [RouterModule]
})
export class PdfReaderRoutingModule { }

View file

@ -0,0 +1,79 @@
<div class="{{theme}}">
<ngx-extended-pdf-viewer
#pdfViewer
[src]="readerService.downloadPdf(this.chapterId)"
height="100vh"
[(page)]="currentPage"
[textLayer]="true"
[useBrowserLocale]="false"
[showHandToolButton]="true"
[showOpenFileButton]="false"
[showPrintButton]="false"
[showBookmarkButton]="false"
[showRotateButton]="false"
[showDownloadButton]="false"
[showPropertiesButton]="false"
[(zoom)]="zoomSetting"
[showSecondaryToolbarButton]="true"
[showBorders]="true"
[theme]="theme"
[formTheme]="theme"
[backgroundColor]="backgroundColor"
[customToolbar]="multiToolbar"
(pageChange)="saveProgress()"
>
</ngx-extended-pdf-viewer>
<ng-template #multiToolbar>
<div style="min-height: 36px" id="toolbarViewer" [ngStyle]="{'background-color': backgroundColor, 'color': fontColor}"> <!--action-bar row g-0 justify-content-between-->
<div id="toolbarViewerLeft">
<pdf-toggle-sidebar></pdf-toggle-sidebar>
<pdf-find-button></pdf-find-button>
<pdf-paging-area></pdf-paging-area>
</div>
<div id="toolbarViewerRight">
<pdf-hand-tool></pdf-hand-tool>
<pdf-select-tool></pdf-select-tool>
<pdf-presentation-mode></pdf-presentation-mode>
<!-- This is not yet supported by the underlying library
<button (click)="toggleBookPageMode()" class="btn btn-icon toolbarButton">
<i class="toolbar-icon fa-solid {{this.bookMode !== 'book' ? 'fa-book' : 'fa-book-open'}}" [ngStyle]="{color: fontColor}" aria-hidden="true"></i>
<span class="visually-hidden">{{this.bookMode !== 'book' ? 'Book Mode' : 'Normal Mode'}}</span>
</button> -->
<button class="btn btn-icon toolbarButton" [ngbTooltip]="bookTitle">
<i class="toolbar-icon fa-solid fa-info" [ngStyle]="{color: fontColor}" aria-hidden="true"></i>
<span class="visually-hidden">
{{bookTitle}}
</span>
</button>
<button *ngIf="incognitoMode" (click)="turnOffIncognito()" class="btn btn-icon toolbarButton">
<i class="toolbar-icon fa fa-glasses" [ngStyle]="{color: fontColor}" aria-hidden="true"></i><span class="visually-hidden">Incognito Mode</span>
</button>
<!-- This is pretty experimental, so it might not work perfectly -->
<button (click)="toggleTheme()" class="btn btn-icon toolbarButton">
<i class="toolbar-icon fa-solid {{this.theme === 'light' ? 'fa-sun' : 'fa-moon'}}" [ngStyle]="{color: fontColor}" aria-hidden="true"></i>
<span class="visually-hidden">{{this.theme === 'light' ? 'Light Theme' : 'Dark Theme'}}</span>
</button>
<button class="btn btn-icon col-2 col-xs-1 toolbarButton" (click)="closeReader()">
<i class="toolbar-icon fa fa-times-circle" aria-hidden="true" [ngStyle]="{color: fontColor}"></i>
<span class="visually-hidden">Close Reader</span>
</button>
<div class="verticalToolbarSeparator hiddenSmallView"></div>
<pdf-toggle-secondary-toolbar></pdf-toggle-secondary-toolbar>
</div>
<pdf-zoom-toolbar ></pdf-zoom-toolbar>
</div>
</ng-template>
</div>

View file

@ -0,0 +1,12 @@
.toolbar-icon {
font-size: 19px;
}
.book-title {
margin: 8px 0 4px !important;
}
// Override since it's not coming from library
::ng-deep #presentationMode {
margin: 3px 0 4px !important;
}

View file

@ -0,0 +1,191 @@
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { PageViewModeType } from 'ngx-extended-pdf-viewer';
import { ToastrService } from 'ngx-toastr';
import { Subject, take } from 'rxjs';
import { BookService } from 'src/app/book-reader/book.service';
import { Chapter } from 'src/app/_models/chapter';
import { User } from 'src/app/_models/user';
import { AccountService } from 'src/app/_services/account.service';
import { MemberService } from 'src/app/_services/member.service';
import { NavService } from 'src/app/_services/nav.service';
import { CHAPTER_ID_DOESNT_EXIST, ReaderService } from 'src/app/_services/reader.service';
import { SeriesService } from 'src/app/_services/series.service';
import { ThemeService } from 'src/app/_services/theme.service';
@Component({
selector: 'app-pdf-reader',
templateUrl: './pdf-reader.component.html',
styleUrls: ['./pdf-reader.component.scss']
})
export class PdfReaderComponent implements OnInit, OnDestroy {
libraryId!: number;
seriesId!: number;
volumeId!: number;
chapterId!: number;
chapter!: Chapter;
user!: User;
/**
* Reading List id. Defaults to -1.
*/
readingListId: number = CHAPTER_ID_DOESNT_EXIST;
/**
* If this is true, no progress will be saved.
*/
incognitoMode: boolean = false;
/**
* If this is true, chapters will be fetched in the order of a reading list, rather than natural series order.
*/
readingListMode: boolean = false;
/**
* Current Page number
*/
currentPage: number = 1;
/**
* Total pages
*/
maxPages: number = 1;
bookTitle: string = '';
zoomSetting: string | number = 'auto';
theme: 'dark' | 'light' = 'light';
themeMap: {[key:string]: {background: string, font: string}} = {
'dark': {'background': '#292929', 'font': '#d9d9d9'},
'light': {'background': '#f9f9f9', 'font': '#5a5a5a'}
}
backgroundColor: string = this.themeMap[this.theme].background;
fontColor: string = this.themeMap[this.theme].font;
isLoading: boolean = false;
/**
* This can't be updated dynamically:
* https://github.com/stephanrauh/ngx-extended-pdf-viewer/issues/1415
*/
bookMode: PageViewModeType = 'multiple';
private readonly onDestroy = new Subject<void>();
constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService,
private seriesService: SeriesService, public readerService: ReaderService,
private navService: NavService, private toastr: ToastrService,
private bookService: BookService, private themeService: ThemeService, private location: Location) {
this.navService.hideNavBar();
this.themeService.clearThemes();
this.navService.hideSideNav();
}
ngOnDestroy(): void {
this.themeService.currentTheme$.pipe(take(1)).subscribe(theme => {
this.themeService.setTheme(theme.name);
});
this.navService.showNavBar();
this.navService.showSideNav();
this.readerService.exitFullscreen();
this.onDestroy.next();
this.onDestroy.complete();
}
ngOnInit(): void {
const libraryId = this.route.snapshot.paramMap.get('libraryId');
const seriesId = this.route.snapshot.paramMap.get('seriesId');
const chapterId = this.route.snapshot.paramMap.get('chapterId');
if (libraryId === null || seriesId === null || chapterId === null) {
this.router.navigateByUrl('/libraries');
return;
}
this.libraryId = parseInt(libraryId, 10);
this.seriesId = parseInt(seriesId, 10);
this.chapterId = parseInt(chapterId, 10);
this.incognitoMode = this.route.snapshot.queryParamMap.get('incognitoMode') === 'true';
const readingListId = this.route.snapshot.queryParamMap.get('readingListId');
if (readingListId != null) {
this.readingListMode = true;
this.readingListId = parseInt(readingListId, 10);
}
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
if (user) {
this.user = user;
this.init();
}
});
}
init() {
this.bookService.getBookInfo(this.chapterId).subscribe(info => {
this.volumeId = info.volumeId;
this.bookTitle = info.bookTitle;
});
this.readerService.getProgress(this.chapterId).subscribe(progress => {
this.currentPage = progress.pageNum || 1;
});
this.seriesService.getChapter(this.chapterId).subscribe(chapter => {
this.maxPages = chapter.pages;
if (this.currentPage >= this.maxPages) {
this.currentPage = this.maxPages - 1;
this.saveProgress();
}
});
}
/**
* Turns off Incognito mode. This can only happen once if the user clicks the icon. This will modify URL state
*/
turnOffIncognito() {
this.incognitoMode = false;
const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId);
window.history.replaceState({}, '', newRoute);
this.toastr.info('Incognito mode is off. Progress will now start being tracked.');
this.saveProgress();
}
toggleTheme() {
if (this.theme === 'dark') {
this.theme = 'light';
} else {
this.theme = 'dark';
}
this.backgroundColor = this.themeMap[this.theme].background;
this.fontColor = this.themeMap[this.theme].font;
}
toggleBookPageMode() {
if (this.bookMode === 'book') {
this.bookMode = 'multiple';
} else {
this.bookMode = 'book';
}
}
saveProgress() {
if (this.incognitoMode) return;
this.readerService.saveProgress(this.seriesId, this.volumeId, this.chapterId, this.currentPage).subscribe(() => {});
}
closeReader() {
if (this.readingListMode) {
this.router.navigateByUrl('lists/' + this.readingListId);
} else {
this.location.back();
}
}
}