feat: scrolling manhwa

This commit is contained in:
2024-07-12 01:32:32 +03:00
parent ccbd9b2f5c
commit 35ff603849
3 changed files with 62 additions and 21 deletions

View File

@@ -13,13 +13,19 @@
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
@if (pages.length > 0 && cachedPages[currentPageIndex]) { @if (pages.length > 0 && cachedPages[currentPageIndex]) {
<div [class]="imageContainerClass"> <div [class]="imageContainerClass">
<app-scale-image [imageSrc]="imageUrl"></app-scale-image> @if (!isManhwa$.value) {
<app-scale-image [imageSrc]="imageUrl"></app-scale-image>
} @else {
@for (page of manhwaPages; track $index) {
<app-scale-image [imageSrc]="page.imageUrl"></app-scale-image>
}
}
</div> </div>
<div class="flex items-center justify-center space-x-4 my-10"> <div class="flex items-center justify-center space-x-4 my-10">
<button (click)="prevPage()" class="p-3 text-white bg-slate-600 w-[100px] rounded-lg"> <button (click)="prevPage()" class="p-3 text-white bg-slate-600 w-[100px] rounded-lg">
</button> </button>
<p>{{ pages[currentPageIndex].slug }} / {{ pages.length }}</p> <p *ngIf="!isManhwa$.value">{{ pages[currentPageIndex].slug }} / {{ pages.length }}</p>
<button (click)="nextPage()" class="p-3 text-white bg-slate-600 w-[100px] rounded-lg"> <button (click)="nextPage()" class="p-3 text-white bg-slate-600 w-[100px] rounded-lg">
</button> </button>

View File

@@ -6,7 +6,7 @@ import { Chapter, Page } from "../../services/parsers/rulib/rulib.chapter.dto";
import { IRulibChapter } from "../../services/parsers/rulib/rulib.chapters.dto"; import { IRulibChapter } from "../../services/parsers/rulib/rulib.chapters.dto";
import { SearchService } from "../../services/search.service"; import { SearchService } from "../../services/search.service";
import { ScaleImageComponent } from "../scale-image/scale-image.component"; import { ScaleImageComponent } from "../scale-image/scale-image.component";
import { CachedPages } from "./reader.dto"; import { CachedPage, CachedPages } from "./reader.dto";
@Component({ @Component({
selector: "app-reader", selector: "app-reader",
@@ -16,6 +16,7 @@ import { CachedPages } from "./reader.dto";
imports: [CommonModule, ScaleImageComponent, RouterLink], imports: [CommonModule, ScaleImageComponent, RouterLink],
}) })
export class ReaderComponent implements AfterViewInit, OnDestroy { export class ReaderComponent implements AfterViewInit, OnDestroy {
//FIXME: Scrolling to top when manhwa
pages: Page[] = []; pages: Page[] = [];
currentPageIndex = 0; currentPageIndex = 0;
cachedPages: CachedPages = {}; cachedPages: CachedPages = {};
@@ -28,7 +29,7 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
url = ""; url = "";
private fromTowards = false; private fromTowards = false;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
private isManhwa$ = new BehaviorSubject<boolean>(false); isManhwa$ = new BehaviorSubject<boolean>(false);
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -41,6 +42,10 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
this.isManhwa$.complete(); this.isManhwa$.complete();
} }
get manhwaPages() {
return Object.values(this.cachedPages) as CachedPage[];
}
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => { this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params) => {
const url = params["url"]; const url = params["url"];
@@ -72,7 +77,16 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
} }
private handleScrollManhwa(event: Event) { private handleScrollManhwa(event: Event) {
console.log(event); if (event.currentTarget) {
if (
(event.currentTarget as HTMLElement).scrollHeight -
(event.currentTarget as HTMLElement).scrollTop <
1000
) {
this.loadPage(this.currentPageIndex + 1);
console.log("load");
}
}
} }
private initManhwaScroll() { private initManhwaScroll() {
@@ -117,13 +131,16 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
if (index >= 0 && index < this.pages.length) { if (index >= 0 && index < this.pages.length) {
this.currentPageIndex = index; this.currentPageIndex = index;
this.cachePage(index); // Кэшируем текущую и соседние страницы this.cachePage(index); // Кэшируем текущую и соседние страницы
this.unloadCachedPages(index); // Сгружаем ненужные страницы из кэша if (!this.isManhwa$.value) this.unloadCachedPages(index); // Сгружаем ненужные страницы из кэша
const container = document.querySelector("app-reader"); if (!this.isManhwa$.value) {
if (container) { console.log("scroll");
container.scrollTo({ const container = document.querySelector("app-reader");
top: 0, if (container) {
behavior: "smooth", container.scrollTo({
}); top: 0,
behavior: "smooth",
});
}
} }
if (!this.cachedPages[index]?.imageData) { if (!this.cachedPages[index]?.imageData) {
// Если страница не закэширована, загружаем её // Если страница не закэширована, загружаем её
@@ -136,7 +153,7 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
// Если страница уже в кэше, просто обновляем изображение // Если страница уже в кэше, просто обновляем изображение
this.updateImage(); this.updateImage();
} }
} else if (index == this.pages.length) { } else if (index == this.pages.length && !this.isManhwa$.value) {
const thisChapterIndex = this.chaptersInfo.findIndex((chapter) => { const thisChapterIndex = this.chaptersInfo.findIndex((chapter) => {
return +chapter.number === this.chapterNum && +chapter.volume === this.chapterVol; return +chapter.number === this.chapterNum && +chapter.volume === this.chapterVol;
}); });
@@ -152,7 +169,7 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
}, },
}); });
} }
} else if (index == -1) { } else if (index == -1 && !this.isManhwa$.value) {
const thisChapterIndex = this.chaptersInfo.findIndex((chapter) => { const thisChapterIndex = this.chaptersInfo.findIndex((chapter) => {
return +chapter.number === this.chapterNum && +chapter.volume === this.chapterVol; return +chapter.number === this.chapterNum && +chapter.volume === this.chapterVol;
}); });
@@ -200,9 +217,15 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
.getImageData(this.searchService.getImageServer() + this.pages[index].url) .getImageData(this.searchService.getImageServer() + this.pages[index].url)
.pipe( .pipe(
map((imageData) => { map((imageData) => {
this.cachedPages[index] = { const url = this.getImageUrl(imageData);
...this.pages[index], this.cachedPages = {
imageData, ...this.cachedPages,
[index]: {
...this.pages[index],
imageData,
imageUrl: url,
isManhwa: +this.pages[index].ratio < 0.5,
},
}; };
}), }),
); );
@@ -218,17 +241,26 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
} }
} }
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);
}
// Обновляем изображение на странице // Обновляем изображение на странице
private updateImage() { private updateImage() {
const currentPage = this.cachedPages[this.currentPageIndex]; const currentPage = this.cachedPages[this.currentPageIndex];
if (currentPage && currentPage.imageData) { if (currentPage && currentPage.imageData && !this.isManhwa$.value) {
const blob = new Blob([currentPage.imageData], { type: "image/jpeg" }); const blob = new Blob([currentPage.imageData], { type: "image/jpeg" });
const urlCreator = window.URL || window.webkitURL; const urlCreator = window.URL || window.webkitURL;
this.imageUrl = urlCreator.createObjectURL(blob); this.imageUrl = urlCreator.createObjectURL(blob);
const imageUrl = this.imageUrl; const imageUrl = this.imageUrl;
const image = new Image(); const image = new Image();
image.onload = () => { image.onload = () => {
this.isManhwa$.next(image.naturalHeight / image.naturalWidth >= 5); this.isManhwa$.next(currentPage.isManhwa);
URL.revokeObjectURL(imageUrl); URL.revokeObjectURL(imageUrl);
}; };
image.src = imageUrl; image.src = imageUrl;
@@ -244,6 +276,6 @@ export class ReaderComponent implements AfterViewInit, OnDestroy {
} }
get imageContainerClass() { get imageContainerClass() {
return `${this.isManhwa ? "h-auto" : "h-[70vh]"} w-[95vw] md:w-[700px] md:h-auto`; return `${this.isManhwa ? "h-auto" : "h-[70vh]"} w-[95vw] md:w-[700px] md:h-auto flex flex-col`;
} }
} }

View File

@@ -1,7 +1,10 @@
import { Page } from "../../services/parsers/rulib/rulib.chapter.dto"; import { Page } from "../../services/parsers/rulib/rulib.chapter.dto";
interface CachedPage extends Page { export interface CachedPage extends Page {
imageData?: Uint8Array; imageData?: Uint8Array;
imageUrl: string;
isManhwa: boolean;
} }
export interface CachedPages { export interface CachedPages {