feat: caching pages

This commit is contained in:
2024-07-07 00:48:54 +05:00
parent 6bad651312
commit 8f5f12ad30
3 changed files with 87 additions and 35 deletions

View File

@@ -1,11 +1,11 @@
<h1>It's reader page</h1>
<div class="flex flex-col items-center">
@if(pages.length > 0){
<img [src]="cachedPages[currentPageIndex].url" [alt]="cachedPages[currentPageIndex].slug" />
<img #mangaImage [alt]="cachedPages[currentPageIndex]?.slug" />
<div class="flex items-center justify-center space-x-4 mb-10">
<button (click)="prevPage()" [disabled]="currentPageIndex === 0" class="p-3 text-white bg-slate-600 w-[100px] mt-5 rounded-lg"></button>
<p>{{pages[currentPageIndex].slug}} / {{pages.length}}</p>
<p>{{pages[currentPageIndex]?.slug}} / {{pages.length}}</p>
<button (click)="nextPage()" [disabled]="currentPageIndex === pages.length - 1" class="p-3 text-white bg-slate-600 w-[100px] mt-5 rounded-lg"></button>
</div>
}
}
</div>

View File

@@ -1,6 +1,7 @@
import { CommonModule } from "@angular/common";
import { AfterViewInit, Component } from "@angular/core";
import { AfterViewInit, Component, ElementRef, ViewChild } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { Observable, map } from "rxjs";
import { Page } from "../../services/parsers/rulib/rulib.chapter.dto";
import { SearchService } from "../../services/search.service";
@@ -14,7 +15,8 @@ import { SearchService } from "../../services/search.service";
export class ReaderComponent implements AfterViewInit {
pages: Page[] = [];
currentPageIndex = 0;
cachedPages: { [key: number]: Page } = {};
cachedPages: { [key: number]: Page & { imageData?: Uint8Array } } = {};
@ViewChild('mangaImage') mangaImage!: ElementRef<HTMLImageElement>;
constructor(
private route: ActivatedRoute,
@@ -38,41 +40,78 @@ export class ReaderComponent implements AfterViewInit {
loadChapter(url: string, chapter: string, volume: string) {
this.searchService.getChapter(url, chapter, volume).subscribe((data) => {
this.pages = data.data.pages;
this.loadPage(0);
this.loadPage(0); // Загрузить первую страницу при открытии главы
});
}
loadPage(index: number) {
if (index >= 0 && index < this.pages.length) {
this.currentPageIndex = index;
this.cachePages(index);
this.cachePage(index); // Кэшируем текущую и соседние страницы
this.unloadCachedPages(index); // Сгружаем ненужные страницы из кэша
if (!this.cachedPages[index]?.imageData) {
// Если страница не закэширована, загружаем её
this.fetchAndCachePage(index).subscribe(() => {
this.updateImage();
});
} else {
// Если страница уже в кэше, просто обновляем изображение
this.updateImage();
}
}
}
if (!this.cachedPages[index]) {
// Кэширование текущей страницы и несколько соседних для ускорения процесса навигации по страницам
private cachePage(index: number) {
const startIndex = Math.max(0, index - 2);
const endIndex = Math.min(this.pages.length - 1, index + 2);
for (let i = startIndex; i <= endIndex; i++) {
if (!this.isPageCached(i)) {
this.fetchAndCachePage(i).subscribe(() => {
if (i === this.currentPageIndex) {
this.updateImage();
}
});
}
}
}
private isPageCached(index: number): boolean {
return !!this.cachedPages[index]?.imageData;
}
// Загрузка и сохранение изображения в кэш
private fetchAndCachePage(index: number): Observable<void> {
return this.searchService
.getImageData(this.searchService.getImageServer() + this.pages[index].url)
.pipe(
map((imageData) => {
this.cachedPages[index] = {
...this.pages[index],
url: this.searchService.getImageServer() + this.pages[index].url,
imageData,
};
})
);
}
// Выгрузка из кэша старых страниц
private unloadCachedPages(index: number) {
for (const key in this.cachedPages) {
const pageIndex = parseInt(key, 10);
if (index - pageIndex > 2) {
delete this.cachedPages[pageIndex];
}
}
}
cachePages(currentIndex: number) {
for (let i = 0; i <= currentIndex && i < this.pages.length; i++) {
if (!this.cachedPages[i]) {
this.cachedPages[i] = {
...this.pages[i],
url: this.searchService.getImageServer() + this.pages[i].url,
};
}
}
for (let i = currentIndex + 1; i <= currentIndex + 3 && i < this.pages.length; i++) {
if (!this.cachedPages[i]) {
this.cachedPages[i] = {
...this.pages[i],
url: this.searchService.getImageServer() + this.pages[i].url,
};
}
// Обновляем изображение на странице
private updateImage() {
const currentPage = this.cachedPages[this.currentPageIndex];
if (this.mangaImage?.nativeElement && currentPage?.imageData) {
const blob = new Blob([currentPage.imageData], { type: 'image/jpeg' });
const urlCreator = window.URL || window.webkitURL;
this.mangaImage.nativeElement.src = urlCreator.createObjectURL(blob);
}
}

View File

@@ -5,12 +5,16 @@ import { IRulibChapterResult } from "./parsers/rulib/rulib.chapter.dto";
import { IRulibChaptersResult } from "./parsers/rulib/rulib.chapters.dto";
import { IRulibDetailResult } from "./parsers/rulib/rulib.detail.dto";
import { Datum } from "./parsers/rulib/rulib.search.dto";
import { HttpClient } from "@angular/common/http";
@Injectable({ providedIn: "root" })
export class SearchService {
private itemsTerm = new BehaviorSubject<Datum[]>([]);
currentItemsTerm = this.itemsTerm.asObservable();
constructor(private parser: LibSocialParserService) {}
constructor(
private parser: LibSocialParserService,
private http: HttpClient,
) {}
search(query: string) {
this.parser.searchManga(query).subscribe((data) => {
@@ -45,4 +49,13 @@ export class SearchService {
getImageServer() {
return this.parser.imageServer;
}
getImageData(imageUrl: string): Observable<Uint8Array>{
return this.http.get(imageUrl, {responseType: 'arraybuffer'}).pipe(
map((arrayBuffer: ArrayBuffer) => {
return new Uint8Array(arrayBuffer);
})
);
}
}