246 lines
7.5 KiB
TypeScript
246 lines
7.5 KiB
TypeScript
import { CommonModule, isPlatformBrowser } from "@angular/common";
|
|
import {
|
|
Component,
|
|
ElementRef,
|
|
HostListener,
|
|
Inject,
|
|
OnDestroy,
|
|
OnInit,
|
|
PLATFORM_ID,
|
|
} from "@angular/core";
|
|
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
|
|
import { Subject, takeUntil } from "rxjs";
|
|
import { Chapter, Page } from "../../services/parsers/rulib/rulib.chapter.dto";
|
|
import { IRulibChapter } from "../../services/parsers/rulib/rulib.chapters.dto";
|
|
import { SearchService } from "../../services/search.service";
|
|
import { ScaleImageComponent } from "../scale-image/scale-image.component";
|
|
import { CachedPage, CachedPages } from "./reader.dto";
|
|
|
|
@Component({
|
|
selector: "app-reader",
|
|
templateUrl: "./reader.component.html",
|
|
styleUrls: ["./reader.component.less"],
|
|
standalone: true,
|
|
imports: [CommonModule, ScaleImageComponent, RouterLink],
|
|
})
|
|
export class ReaderComponent implements OnInit, OnDestroy {
|
|
//FIXME: Scrolling to top when manhwa
|
|
pages: Page[] = [];
|
|
currentPageIndex = 0;
|
|
cachedPages: CachedPages = new Map<number, CachedPage>();
|
|
imageUrl: string = "";
|
|
isManhwa = false;
|
|
currentChapterInfo: Chapter | null = null;
|
|
url = "";
|
|
private chaptersInfo: IRulibChapter[] = [];
|
|
private chapterNum: number = 0;
|
|
private chapterVol: number = 0;
|
|
private fromTowards = false;
|
|
private destroy$ = new Subject<void>();
|
|
|
|
constructor(
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private searchService: SearchService,
|
|
public host: ElementRef,
|
|
@Inject(PLATFORM_ID) private platformId: object,
|
|
) {}
|
|
ngOnDestroy(): void {
|
|
this.destroy$.next();
|
|
this.destroy$.complete();
|
|
}
|
|
|
|
get manhwaPages() {
|
|
return this.cachedPages.values();
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
|
|
const url = params["url"];
|
|
const chapter = params["chapter"];
|
|
const volume = params["volume"];
|
|
const fromTowards = params["from_towards"];
|
|
if (url && chapter && volume) {
|
|
if (fromTowards) this.fromTowards = Boolean(+fromTowards);
|
|
else this.fromTowards = false;
|
|
this.chapterNum = +chapter;
|
|
this.chapterVol = +volume;
|
|
this.url = url;
|
|
this.searchService
|
|
.getChapters(url)
|
|
.pipe(takeUntil(this.destroy$))
|
|
.subscribe((data) => {
|
|
this.chaptersInfo = data.data;
|
|
});
|
|
this.searchService
|
|
.isManhwa(url)
|
|
.pipe(takeUntil(this.destroy$))
|
|
.subscribe((data) => {
|
|
this.isManhwa = data;
|
|
this.loadChapter(url, chapter, volume);
|
|
});
|
|
} else {
|
|
this.router.navigate(["/"]);
|
|
}
|
|
});
|
|
}
|
|
|
|
public isHostScrollable(): boolean {
|
|
if (isPlatformBrowser(this.platformId)) {
|
|
const style = window.getComputedStyle(this.host.nativeElement);
|
|
return (
|
|
style.getPropertyValue("overflow") === "auto" ||
|
|
style.getPropertyValue("overflow-y") === "scroll"
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
backToTitle() {
|
|
this.router.navigate(["/", "detail"], { queryParams: { url: this.url } });
|
|
}
|
|
|
|
loadChapter(url: string, chapter: string, volume: string) {
|
|
this.searchService
|
|
.getChapter(url, chapter, volume)
|
|
.pipe(takeUntil(this.destroy$))
|
|
.subscribe({
|
|
next: (data) => {
|
|
this.currentChapterInfo = data.data;
|
|
this.pages = data.data.pages;
|
|
this.pages.forEach((page, index) => {
|
|
this.cachedPages.set(index, {
|
|
...page,
|
|
imageUrl: "",
|
|
isManhwa: this.isManhwa,
|
|
index,
|
|
});
|
|
});
|
|
this.loadPage(0);
|
|
if (this.fromTowards) {
|
|
this.currentPageIndex = this.pages.length - 1;
|
|
this.loadPage(this.pages.length - 1); // Загрузить последнюю страницу при открытии главы
|
|
} else this.loadPage(0); // Загрузить первую страницу при открытии главы
|
|
},
|
|
error: (error) => {
|
|
console.log(error);
|
|
this.router.navigate(["/", "detail"], { queryParams: { url: this.url } });
|
|
},
|
|
});
|
|
}
|
|
|
|
public intersectionLoadPage(id: number) {
|
|
const data = Array.from(this.cachedPages.values()).find((page) => page.id === id)!;
|
|
this.fetchPage(data);
|
|
}
|
|
|
|
private loadPageByIndex(index: number) {
|
|
const data = this.cachedPages.get(index)!;
|
|
this.fetchPage(data);
|
|
}
|
|
|
|
get currentPage() {
|
|
return this.cachedPages.get(this.currentPageIndex);
|
|
}
|
|
|
|
private fetchPage(page: CachedPage) {
|
|
if (!page.imageUrl)
|
|
this.searchService
|
|
.getImageData(this.searchService.getImageServer() + page.url)
|
|
.pipe(takeUntil(this.destroy$))
|
|
.subscribe((data) => {
|
|
const url = this.getImageUrl(data);
|
|
this.cachedPages.set(page.index, {
|
|
...page,
|
|
imageUrl: url,
|
|
});
|
|
});
|
|
}
|
|
|
|
private cachePage(index: number) {
|
|
for (let i = index; i < Math.min(index + 2, this.cachedPages.size); i++) {
|
|
this.loadPageByIndex(i);
|
|
}
|
|
}
|
|
|
|
private clearCache(index: number) {
|
|
for (let i = 0; i <= index; i++) {
|
|
const imgUrl = this.cachedPages.get(i)!.imageUrl;
|
|
this.cachedPages.get(i)!.imageUrl = "";
|
|
URL.revokeObjectURL(imgUrl);
|
|
}
|
|
}
|
|
|
|
loadPage(index: number) {
|
|
if (index >= 0 && index < this.pages.length) {
|
|
this.currentPageIndex = index;
|
|
this.cachePage(index);
|
|
this.clearCache(index - 2);
|
|
} else if (index == this.cachedPages.size) {
|
|
const thisChapterIndex = this.chaptersInfo.findIndex((chapter) => {
|
|
return +chapter.number === this.chapterNum && +chapter.volume === this.chapterVol;
|
|
});
|
|
const nextChapterIndex = Math.min(thisChapterIndex + 1, this.chaptersInfo.length - 1);
|
|
const nextChapter = this.chaptersInfo[nextChapterIndex];
|
|
if (nextChapter !== this.chaptersInfo[thisChapterIndex]) {
|
|
this.cachedPages.clear();
|
|
this.router.navigate(["/", "reader"], {
|
|
queryParams: {
|
|
url: this.url,
|
|
chapter: nextChapter.number,
|
|
volume: nextChapter.volume,
|
|
},
|
|
});
|
|
}
|
|
} else if (index == -1) {
|
|
const thisChapterIndex = this.chaptersInfo.findIndex((chapter) => {
|
|
return +chapter.number === this.chapterNum && +chapter.volume === this.chapterVol;
|
|
});
|
|
const prevChapterIndex = Math.max(thisChapterIndex - 1, 0);
|
|
const prevChapter = this.chaptersInfo[prevChapterIndex];
|
|
if (prevChapter !== this.chaptersInfo[thisChapterIndex]) {
|
|
this.cachedPages.clear();
|
|
this.router.navigate(["/", "reader"], {
|
|
queryParams: {
|
|
url: this.url,
|
|
chapter: prevChapter.number,
|
|
volume: prevChapter.volume,
|
|
from_towards: 1,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@HostListener("window:keydown", ["$event"])
|
|
onKeydown(event: KeyboardEvent) {
|
|
if (event.key === "ArrowRight") {
|
|
this.nextPage();
|
|
} else if (event.key === "ArrowLeft") {
|
|
this.prevPage();
|
|
}
|
|
}
|
|
|
|
private getImageUrl(imageData?: Uint8Array): string {
|
|
if (!imageData) {
|
|
return "";
|
|
}
|
|
const blob = new Blob([imageData], { type: "image/jpeg" });
|
|
const urlCreator = window.URL || window.webkitURL;
|
|
return urlCreator.createObjectURL(blob);
|
|
}
|
|
|
|
nextPage() {
|
|
this.loadPage(this.currentPageIndex + 1);
|
|
}
|
|
|
|
prevPage() {
|
|
this.loadPage(this.currentPageIndex - 1);
|
|
}
|
|
|
|
get imageContainerClass() {
|
|
return `${this.isManhwa ? "h-auto" : "h-[70vh]"} w-[95vw] md:w-[450px] flex flex-col`;
|
|
}
|
|
}
|