feat: implement loaders for EPUB and PDF readers (#38)

This commit is contained in:
aditya.chandel
2025-03-28 02:27:23 -06:00
committed by Aditya Chandel
parent fac3f8c819
commit 6a4b6b2bcb
4 changed files with 127 additions and 95 deletions

View File

@@ -1,35 +1,37 @@
<div class="epub-viewer-container">
<div #epubContainer class="epub-viewer-container">
<p-progressSpinner *ngIf="isLoading"></p-progressSpinner>
<div *ngIf="!isLoading">
<p-button class="menu-toggle-button" (click)="toggleDrawer()" icon="pi pi-bars" severity="secondary"></p-button>
<p-drawer [(visible)]="isDrawerVisible" [modal]="false" [position]="'left'" header="Chapters">
<ul class="chapter-list">
<li *ngFor="let chapter of chapters" (click)="navigateToChapter(chapter); $event.stopPropagation()" class="chapter-item">
{{ chapter.label }}
</li>
</ul>
</p-drawer>
<p-button class="menu-toggle-button" (click)="toggleDrawer()" icon="pi pi-bars" severity="secondary"></p-button>
<p-drawer [(visible)]="isDrawerVisible" [modal]="false" [position]="'left'" header="Chapters">
<ul class="chapter-list">
<li *ngFor="let chapter of chapters" (click)="navigateToChapter(chapter); $event.stopPropagation()" class="chapter-item">
{{ chapter.label }}
</li>
</ul>
</p-drawer>
<p-button class="settings-toggle-button" (click)="toggleSettingsDrawer()" icon="pi pi-cog" severity="secondary"></p-button>
<p-drawer [(visible)]="isSettingsDrawerVisible" [modal]="false" [position]="'right'" header="Settings">
<div class="flex flex-col">
<div class="settings-content">
<p>Font Size: {{ fontSize }}%</p>
<div class="flex flex-row gap-4">
<p-button icon="pi pi-minus" (click)="decreaseFontSize()" severity="info"></p-button>
<p-button icon="pi pi-plus" (click)="increaseFontSize()" severity="info"></p-button>
<p-button class="settings-toggle-button" (click)="toggleSettingsDrawer()" icon="pi pi-cog" severity="secondary"></p-button>
<p-drawer [(visible)]="isSettingsDrawerVisible" [modal]="false" [position]="'right'" header="Settings">
<div class="flex flex-col">
<div class="settings-content">
<p>Font Size: {{ fontSize }}%</p>
<div class="flex flex-row gap-4">
<p-button icon="pi pi-minus" (click)="decreaseFontSize()" severity="info"></p-button>
<p-button icon="pi pi-plus" (click)="increaseFontSize()" severity="info"></p-button>
</div>
<p-divider></p-divider>
<label for="font-type">Font Type:</label>
<p-select id="font-type" [options]="fontTypes" [(ngModel)]="selectedFontType" (onChange)="changeFontType()"></p-select>
<p-divider></p-divider>
<label for="theme">Theme:</label>
<p-select id="theme" [options]="themes" [(ngModel)]="selectedTheme" (onChange)="changeThemes()"></p-select>
</div>
<p-divider></p-divider>
<label for="font-type">Font Type:</label>
<p-select id="font-type" [options]="fontTypes" [(ngModel)]="selectedFontType" (onChange)="changeFontType()"></p-select>
<p-divider></p-divider>
<label for="theme">Theme:</label>
<p-select id="theme" [options]="themes" [(ngModel)]="selectedTheme" (onChange)="changeThemes()"></p-select>
</div>
</div>
</p-drawer>
</p-drawer>
<div id="epubContainer" #epubContainer></div>
<div id="epubContainer" #epubContainer></div>
<button class="epub-controls-left" (click)="prevPage()" ></button>
<button class="epub-controls-right" (click)="nextPage()"></button>
<button class="epub-controls-left" (click)="prevPage()" ></button>
<button class="epub-controls-right" (click)="nextPage()"></button>
</div>
</div>

View File

@@ -2,16 +2,17 @@ import {Component, ElementRef, inject, OnDestroy, OnInit, ViewChild} from '@angu
import ePub from 'epubjs';
import {Drawer} from 'primeng/drawer';
import {Button} from 'primeng/button';
import {NgForOf} from '@angular/common';
import {NgForOf, NgIf} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {Divider} from 'primeng/divider';
import {ActivatedRoute} from '@angular/router';
import {Book, BookSetting} from '../../../model/book.model';
import {BookService} from '../../../service/book.service';
import {filter, forkJoin, take} from 'rxjs';
import {AppSettingsService} from '../../../../core/service/app-settings.service';
import {forkJoin} from 'rxjs';
import {Select} from 'primeng/select';
import {UserService} from '../../../../user.service';
import {ProgressSpinner} from 'primeng/progressspinner';
import {MessageService} from 'primeng/api';
const FALLBACK_EPUB_SETTINGS = {
fontSize: 150,
@@ -25,12 +26,13 @@ const FALLBACK_EPUB_SETTINGS = {
selector: 'app-epub-viewer',
templateUrl: './epub-viewer.component.html',
styleUrls: ['./epub-viewer.component.scss'],
imports: [Drawer, Button, NgForOf, FormsModule, Divider, Select],
imports: [Drawer, Button, NgForOf, FormsModule, Divider, Select, ProgressSpinner, NgIf],
standalone: true
})
export class EpubViewerComponent implements OnInit, OnDestroy {
@ViewChild('epubContainer', {static: true}) epubContainer!: ElementRef;
@ViewChild('epubContainer', {static: false}) epubContainer!: ElementRef;
isLoading = true;
chapters: { label: string; href: string }[] = [];
isDrawerVisible = false;
isSettingsDrawerVisible = false;
@@ -61,11 +63,13 @@ export class EpubViewerComponent implements OnInit, OnDestroy {
private route = inject(ActivatedRoute);
private userService = inject(UserService);
private bookService = inject(BookService);
private messageService = inject(MessageService);
epub!: Book;
ngOnInit(): void {
this.route.paramMap.subscribe((params) => {
this.isLoading = true;
const bookId = +params.get('bookId')!;
const myself$ = this.userService.getMyself();
@@ -73,62 +77,69 @@ export class EpubViewerComponent implements OnInit, OnDestroy {
const epubData$ = this.bookService.getFileContent(bookId);
const bookSetting$ = this.bookService.getBookSetting(bookId);
forkJoin([myself$, epub$, epubData$, bookSetting$]).subscribe((results) => {
const myself = results[0];
const epub = results[1];
const epubData = results[2];
const individualSetting = results[3]?.epubSettings;
forkJoin([myself$, epub$, epubData$, bookSetting$]).subscribe({
next: (results) => {
const myself = results[0];
const epub = results[1];
const epubData = results[2];
const individualSetting = results[3]?.epubSettings;
this.epub = epub;
const fileReader = new FileReader();
this.epub = epub;
const fileReader = new FileReader();
fileReader.onload = () => {
this.book = ePub(fileReader.result as ArrayBuffer);
fileReader.onload = () => {
this.book = ePub(fileReader.result as ArrayBuffer);
this.book.loaded.navigation.then((nav: any) => {
this.chapters = nav.toc.map((chapter: any) => ({
label: chapter.label,
href: chapter.href,
}));
});
this.book.loaded.navigation.then((nav: any) => {
this.chapters = nav.toc.map((chapter: any) => ({
label: chapter.label,
href: chapter.href,
}));
});
this.rendition = this.book.renderTo(this.epubContainer.nativeElement, {
flow: 'paginated',
width: '100%',
height: '100%',
allowScriptedContent: true,
});
this.rendition = this.book.renderTo(this.epubContainer.nativeElement, {
flow: 'paginated',
width: '100%',
height: '100%',
allowScriptedContent: true,
});
if (this.epub?.epubProgress) {
this.rendition.display(this.epub.epubProgress);
} else {
this.rendition.display();
}
if (this.epub?.epubProgress) {
this.rendition.display(this.epub.epubProgress);
} else {
this.rendition.display();
}
this.themesMap.forEach((theme, name) => {
this.rendition.themes.register(name, theme);
});
this.themesMap.forEach((theme, name) => {
this.rendition.themes.register(name, theme);
});
let globalOrIndividual = myself.bookPreferences.perBookSetting.epub;
if (globalOrIndividual === 'Global') {
this.selectedTheme = myself.bookPreferences.epubReaderSetting.theme || FALLBACK_EPUB_SETTINGS.theme;
this.selectedFontType = myself.bookPreferences.epubReaderSetting.font || FALLBACK_EPUB_SETTINGS.fontType;
this.fontSize = myself.bookPreferences.epubReaderSetting.fontSize || FALLBACK_EPUB_SETTINGS.fontSize;
} else {
this.selectedTheme = individualSetting?.theme || myself.bookPreferences.epubReaderSetting.theme || FALLBACK_EPUB_SETTINGS.theme;
this.selectedFontType = individualSetting?.font || myself.bookPreferences.epubReaderSetting.font || FALLBACK_EPUB_SETTINGS.fontType;
this.fontSize = individualSetting?.fontSize || myself.bookPreferences.epubReaderSetting.fontSize || FALLBACK_EPUB_SETTINGS.fontSize;
}
let globalOrIndividual = myself.bookPreferences.perBookSetting.epub;
if (globalOrIndividual === 'Global') {
this.selectedTheme = myself.bookPreferences.epubReaderSetting.theme || FALLBACK_EPUB_SETTINGS.theme;
this.selectedFontType = myself.bookPreferences.epubReaderSetting.font || FALLBACK_EPUB_SETTINGS.fontType;
this.fontSize = myself.bookPreferences.epubReaderSetting.fontSize || FALLBACK_EPUB_SETTINGS.fontSize;
} else {
this.selectedTheme = individualSetting?.theme || myself.bookPreferences.epubReaderSetting.theme || FALLBACK_EPUB_SETTINGS.theme;
this.selectedFontType = individualSetting?.font || myself.bookPreferences.epubReaderSetting.font || FALLBACK_EPUB_SETTINGS.fontType;
this.fontSize = individualSetting?.fontSize || myself.bookPreferences.epubReaderSetting.fontSize || FALLBACK_EPUB_SETTINGS.fontSize;
}
this.rendition.themes.select(this.selectedTheme);
this.rendition.themes.fontSize(`${this.fontSize}%`);
this.rendition.themes.font(this.selectedFontType);
this.rendition.themes.select(this.selectedTheme);
this.rendition.themes.fontSize(`${this.fontSize}%`);
this.rendition.themes.font(this.selectedFontType);
this.setupKeyListener();
this.trackProgress();
};
this.setupKeyListener();
this.trackProgress();
this.isLoading = false;
};
fileReader.readAsArrayBuffer(epubData);
fileReader.readAsArrayBuffer(epubData);
},
error: () => {
this.messageService.add({severity: 'error', summary: 'Error', detail: 'Failed to load the book'});
this.isLoading = false;
}
});
});
}

View File

@@ -1,4 +1,9 @@
<div *ngIf="isLoading" class="flex justify-center items-center h-screen relative">
<p-progressSpinner></p-progressSpinner>
</div>
<ngx-extended-pdf-viewer
*ngIf="!isLoading"
[src]="bookData"
[handTool]="handTool"
[height]="'auto'"

View File

@@ -7,14 +7,19 @@ import {filter, forkJoin, Subscription, take} from 'rxjs';
import {BookSetting, PdfViewerSetting} from '../../model/book.model';
import {PdfSettings} from '../../../core/model/app-settings.model';
import {UserService} from '../../../user.service';
import {NgIf} from '@angular/common';
import {ProgressSpinner} from 'primeng/progressspinner';
import {MessageService} from 'primeng/api';
@Component({
selector: 'app-pdf-viewer',
standalone: true,
imports: [NgxExtendedPdfViewerModule],
imports: [NgxExtendedPdfViewerModule, NgIf, ProgressSpinner],
templateUrl: './pdf-viewer.component.html',
})
export class PdfViewerComponent implements OnInit, OnDestroy {
isLoading = true;
handTool = true;
rotation: 0 | 90 | 180 | 270 = 0;
@@ -28,10 +33,12 @@ export class PdfViewerComponent implements OnInit, OnDestroy {
private bookService = inject(BookService);
private userService = inject(UserService);
private messageService = inject(MessageService);
private route = inject(ActivatedRoute);
ngOnInit(): void {
this.route.paramMap.subscribe((params) => {
this.isLoading = true;
this.bookId = +params.get('bookId')!;
const myself$ = this.userService.getMyself();
@@ -39,22 +46,29 @@ export class PdfViewerComponent implements OnInit, OnDestroy {
const bookSetting$ = this.bookService.getBookSetting(this.bookId);
const pdfData$ = this.bookService.getFileContent(this.bookId);
forkJoin([book$, bookSetting$, pdfData$, myself$]).subscribe((results) => {
const pdfMeta = results[0];
const pdfPrefs = results[1];
const pdfData = results[2];
const myself = results[3];
forkJoin([book$, bookSetting$, pdfData$, myself$]).subscribe({
next: (results) => {
const pdfMeta = results[0];
const pdfPrefs = results[1];
const pdfData = results[2];
const myself = results[3];
let globalOrIndividual = myself.bookPreferences.perBookSetting.pdf;
if (globalOrIndividual === 'Global') {
this.zoom = myself.bookPreferences.pdfReaderSetting.pageZoom || 'page-fit';
this.spread = myself.bookPreferences.pdfReaderSetting.pageSpread || 'odd';
} else {
this.zoom = pdfPrefs.pdfSettings?.zoom || myself.bookPreferences.pdfReaderSetting.pageZoom || 'page-fit';
this.spread = pdfPrefs.pdfSettings?.spread || myself.bookPreferences.pdfReaderSetting.pageSpread || 'odd';
let globalOrIndividual = myself.bookPreferences.perBookSetting.pdf;
if (globalOrIndividual === 'Global') {
this.zoom = myself.bookPreferences.pdfReaderSetting.pageZoom || 'page-fit';
this.spread = myself.bookPreferences.pdfReaderSetting.pageSpread || 'odd';
} else {
this.zoom = pdfPrefs.pdfSettings?.zoom || myself.bookPreferences.pdfReaderSetting.pageZoom || 'page-fit';
this.spread = pdfPrefs.pdfSettings?.spread || myself.bookPreferences.pdfReaderSetting.pageSpread || 'odd';
}
this.page = pdfMeta.pdfProgress || 1;
this.bookData = pdfData;
this.isLoading = false;
},
error: () => {
this.messageService.add({severity: 'error', summary: 'Error', detail: 'Failed to load the book'});
this.isLoading = false;
}
this.page = pdfMeta.pdfProgress || 1;
this.bookData = pdfData;
});
});
}