feat: scrolling manhwa
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user