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:
parent
384fac68c4
commit
3ab3a10ae7
45 changed files with 2309 additions and 208 deletions
21
UI/Web/src/app/pdf-reader/pdf-reader.module.ts
Normal file
21
UI/Web/src/app/pdf-reader/pdf-reader.module.ts
Normal 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 { }
|
||||
17
UI/Web/src/app/pdf-reader/pdf-reader.router.module.ts
Normal file
17
UI/Web/src/app/pdf-reader/pdf-reader.router.module.ts
Normal 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 { }
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
191
UI/Web/src/app/pdf-reader/pdf-reader/pdf-reader.component.ts
Normal file
191
UI/Web/src/app/pdf-reader/pdf-reader/pdf-reader.component.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue