diff --git a/API/API.csproj b/API/API.csproj index 82f0e9b4d..60e3c9ebd 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -46,7 +46,7 @@ - + @@ -63,14 +63,14 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/API/config/appsettings.Development.json b/API/config/appsettings.Development.json index 0d7c12bda..78d892e05 100644 --- a/API/config/appsettings.Development.json +++ b/API/config/appsettings.Development.json @@ -5,7 +5,7 @@ "TokenKey": "super secret unguessable key", "Logging": { "LogLevel": { - "Default": "Debug", + "Default": "Critical", "Microsoft": "Information", "Microsoft.Hosting.Lifetime": "Error", "Hangfire": "Information", diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 1d3c93525..5d6127b13 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/UI/Web/src/app/_services/action-factory.service.ts b/UI/Web/src/app/_services/action-factory.service.ts index 8532fbdc9..5d728f80c 100644 --- a/UI/Web/src/app/_services/action-factory.service.ts +++ b/UI/Web/src/app/_services/action-factory.service.ts @@ -65,6 +65,10 @@ export enum Action { * Open Series detail page for said series */ ViewSeries = 13, + /** + * Open the reader for entity + */ + Read = 14, } export interface ActionItem { diff --git a/UI/Web/src/app/app.component.ts b/UI/Web/src/app/app.component.ts index 01d9baafa..0705db710 100644 --- a/UI/Web/src/app/app.component.ts +++ b/UI/Web/src/app/app.component.ts @@ -36,13 +36,11 @@ export class AppComponent implements OnInit { } @HostListener('window:resize', ['$event']) - onResize(){ - this.setDocHeight(); - } - @HostListener('window:orientationchange', ['$event']) - onOrientationChange() { - this.setDocHeight(); + setDocHeight() { + // Sets a CSS variable for the actual device viewport height. Needed for mobile dev. + const vh = window.innerHeight * 0.01; + this.document.documentElement.style.setProperty('--vh', `${vh}px`); } ngOnInit(): void { @@ -59,10 +57,4 @@ export class AppComponent implements OnInit { this.libraryService.getLibraryNames().pipe(take(1)).subscribe(() => {/* No Operation */}); } } - - setDocHeight() { - // Sets a CSS variable for the actual device viewport height. Needed for mobile dev. - let vh = window.innerHeight * 0.01; - this.document.documentElement.style.setProperty('--vh', `${vh}px`); - } } diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.html b/UI/Web/src/app/book-reader/book-reader/book-reader.component.html index 7750b91b5..9a10e7ce8 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.html +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.html @@ -72,24 +72,26 @@ -
+
-
-
-
+
-
-
+
diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss b/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss index 568ce5210..be2b9c444 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss @@ -130,6 +130,7 @@ $action-bar-height: 38px; overflow: auto; height: calc(var(--vh, 1vh) * 100); position: relative; + // This is completely invisible, everything else renders over it &.column-layout-1 { height: calc(var(--vh) * 100); @@ -145,8 +146,11 @@ $action-bar-height: 38px; //overflow: auto; // This will break progress reporting height: 100vh; padding-top: $action-bar-height; + padding-bottom: $action-bar-height; position: relative; + //background-color: green !important; + &.column-layout-1 { height: calc((var(--vh, 1vh) * 100) - $action-bar-height); } @@ -154,12 +158,20 @@ $action-bar-height: 38px; &.column-layout-2 { height: calc((var(--vh, 1vh) * 100) - $action-bar-height); } + + &.immersive { + height: calc((var(--vh, 1vh) * 100)); + //padding-top: 0px; + //padding-bottom: 0px; + } } .book-container { position: relative; height: 100%; + //background-color: purple !important; + &.column-layout-1 { height: calc((var(--vh, 1vh) * 100) - $action-bar-height); } @@ -173,13 +185,18 @@ $action-bar-height: 38px; position: relative; padding: 20px 0; margin: 0px 0px; + //background-color: red !important; &.column-layout-1 { - height: calc((var(--vh) * 100) - calc($action-bar-height * 2)); + height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2 } &.column-layout-2 { - height: calc((var(--vh) * 100) - calc($action-bar-height * 2)); + height: calc((var(--vh) * 100) - calc($action-bar-height)); // * 2 + } + + &.immersive { + height: calc((var(--vh, 1vh) * 100) - $action-bar-height); } a, :link { @@ -203,6 +220,12 @@ $action-bar-height: 38px; box-shadow: var(--drawer-pagination-horizontal-rule); } +.bottom-bar { + position: fixed; + width: 100%; + bottom: 0px; +} + // This is essentially fitting the text to height and when you press next you are scrolling over by page width @@ -213,10 +236,6 @@ $action-bar-height: 38px; overflow: hidden; word-break: break-word; overflow-wrap: break-word; - - &.debug { - column-rule: 20px solid rebeccapurple; - } } @@ -229,10 +248,6 @@ $action-bar-height: 38px; overflow: hidden; word-break: break-word; overflow-wrap: break-word; - - &.debug { - column-rule: 20px solid rebeccapurple; - } } @@ -249,7 +264,8 @@ $action-bar-height: 38px; // This is applied to images in the backend ::ng-deep .kavita-scale-width-container { width: auto; - max-height: calc((var(--vh)*100) - 116px) !important; + // * 4 is just for extra buffer which is needed based on testing. --book-reader-content-max-height is set by us on calculation of columnHeight + max-height: calc(var(--book-reader-content-max-height) - ($action-bar-height * 4)), calc((var(--vh)*100) - ($action-bar-height * 4)) !important; } // This is applied to images in the backend @@ -272,13 +288,21 @@ $action-bar-height: 38px; .right { position: absolute; - right: 0px; // with scrollbar: 17px + right: 0px; top: $action-bar-height; - width: 20%; // with scrollbar: 18% - - z-index: 2; + width: 20%; + z-index: 3; cursor: pointer; background: transparent; + border-color: transparent; + border: none !important; + opacity: 0; + outline: none; + //background-color: aqua; + + &.immersive { + top: 0px; + } } // This class pushes the click area to the left a bit to let users click the scrollbar @@ -287,9 +311,18 @@ $action-bar-height: 38px; right: 17px; top: $action-bar-height; width: 18%; - z-index: 2; + z-index: 3; cursor: pointer; background: transparent; + border-color: transparent; + border: none !important; + opacity: 0; + outline: none; + //background-color: aqua; + + &.immersive { + top: 0px; + } } .left { @@ -298,16 +331,24 @@ $action-bar-height: 38px; top: $action-bar-height; width: 20%; background: transparent; - - z-index: 2; + border-color: transparent; + border: none !important; + z-index: 3; cursor: pointer; + opacity: 0; + outline: none; + //background-color: aqua; + + &.immersive { + top: 0px; + } } .highlight { - background-color: rgba(65, 225, 100, 0.5) !important; - animation: fadein .5s both; + background-color: rgba(65, 225, 100, 0.5) !important; + animation: fadein .5s both; } .highlight-2 { background-color: rgba(65, 105, 225, 0.5) !important; @@ -333,13 +374,13 @@ $action-bar-height: 38px; background-color: unset; &:hover, &:focus { - border-color: var(--br-actionbar-button-hover-border-color); // #545b62; + border-color: var(--br-actionbar-button-hover-border-color); } } span { background-color: unset; - color: var(--br-actionbar-button-text-color); // #6c757d; + color: var(--br-actionbar-button-text-color); } i { diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts index ca438e477..c38fbcabb 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts @@ -352,7 +352,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { get ColumnHeight() { if (this.layoutMode !== BookPageLayoutMode.Default) { // Take the height after page loads, subtract the top/bottom bar - return this.windowHeight - (this.topOffset *2) + 'px'; + const height = this.windowHeight - (this.topOffset * 2); + this.document.documentElement.style.setProperty('--book-reader-content-max-height', `${height}px`); + return height + 'px'; } return 'unset'; } @@ -370,10 +372,11 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { get PageHeightForPagination() { if (this.layoutMode === BookPageLayoutMode.Default) { - return (this.readingSectionElemRef?.nativeElement?.scrollHeight || 0) - (this.topOffset * 2) + 'px'; + return (this.readingSectionElemRef?.nativeElement?.scrollHeight || 0) - ((this.topOffset * (this.immersiveMode ? 0 : 1)) * 2) + 'px'; } - return this.ColumnHeight; + if (this.immersiveMode) return this.windowHeight + 'px'; + return (this.windowHeight) - (this.topOffset * 2) + 'px'; } @@ -808,7 +811,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { const images = this.readingSectionElemRef?.nativeElement.querySelectorAll('img') || []; if (this.layoutMode !== BookPageLayoutMode.Default) { - const height = this.ColumnHeight; + const height = (parseInt(this.ColumnHeight.replace('px', ''), 10) - (this.topOffset * 2)) + 'px'; Array.from(images).forEach(img => { this.renderer.setStyle(img, 'max-height', height); }); @@ -1209,6 +1212,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { return; } setTimeout(() => {this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.reader.nativeElement.clientHeight;}); + + // When I switch layout, I might need to resume the progress point. + if (mode === BookPageLayoutMode.Default) { + const lastSelector = this.lastSeenScrollPartPath; + setTimeout(() => this.scrollTo(lastSelector)); + } } updateReadingDirection(readingDirection: ReadingDirection) { @@ -1220,6 +1229,19 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { if (this.immersiveMode && !this.drawerOpen) { this.actionBarVisible = false; } + + this.updateReadingSectionHeight(); + } + + updateReadingSectionHeight() { + setTimeout(() => { + console.log('setting height on ', this.readingSectionElemRef) + if (this.immersiveMode) { + this.renderer.setStyle(this.readingSectionElemRef, 'height', 'calc(var(--vh, 1vh) * 100)', RendererStyleFlags2.Important); + } else { + this.renderer.setStyle(this.readingSectionElemRef, 'height', 'calc(var(--vh, 1vh) * 100 - ' + this.topOffset + 'px)', RendererStyleFlags2.Important); + } + }); } // Table of Contents diff --git a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html index 819a09a3e..18b32fef3 100644 --- a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html +++ b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.html @@ -43,24 +43,29 @@
-
- +
+ + + + + No Summary available. +
- +
- - {{chapter.pages | number:''}} Pages + + {{totalPages | number:''}} Pages
- +
- + {{chapterMetadata.releaseDate | date:'shortDate'}}
@@ -69,7 +74,7 @@
- + {{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
@@ -78,7 +83,7 @@
- + {{chapterMetadata.wordCount | compactNumber}} Words
@@ -88,12 +93,30 @@
- + {{ageRating}}
+ + +
+
+ + {{chapter.created | date:'short' || '-'}} + +
+
+ + +
+
+ + {{data.id}} + +
+
@@ -182,24 +205,6 @@
  • {{tabs[TabID.Files].title}} -
    - -
    - - Created: {{chapter.created | date:'short' || '-'}} - -
    -
    - - -
    -
    - - ID: {{data.id}} - -
    -
    -

    {{utilityService.formatChapterName(libraryType) + 's'}}

    • diff --git a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.scss b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.scss index 2db0d16b3..39570d23f 100644 --- a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.scss +++ b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.scss @@ -12,5 +12,5 @@ .tab-content { overflow: auto; - height: calc(45vh - 63px); // drawer height - offcanvas heading height + height: calc(40vh - 63px); // drawer height - offcanvas heading height } diff --git a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.ts b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.ts index ba43deba8..ecb9a38b7 100644 --- a/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.ts +++ b/UI/Web/src/app/cards/card-detail-drawer/card-detail-drawer.component.ts @@ -2,7 +2,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { NgbActiveOffcanvas } from '@ng-bootstrap/ng-bootstrap'; import { ToastrService } from 'ngx-toastr'; -import { Observable, of, take } from 'rxjs'; +import { finalize, Observable, of, take, takeWhile, tap } from 'rxjs'; +import { Download } from 'src/app/shared/_models/download'; +import { DownloadService } from 'src/app/shared/_services/download.service'; import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service'; import { Chapter } from 'src/app/_models/chapter'; import { ChapterMetadata } from 'src/app/_models/chapter-metadata'; @@ -18,7 +20,7 @@ import { ActionService } from 'src/app/_services/action.service'; import { ImageService } from 'src/app/_services/image.service'; import { LibraryService } from 'src/app/_services/library.service'; import { MetadataService } from 'src/app/_services/metadata.service'; -import { MAX_PAGES_PER_MINUTE, MAX_WORDS_PER_HOUR, MIN_PAGES_PER_MINUTE, MIN_WORDS_PER_HOUR, ReaderService } from 'src/app/_services/reader.service'; +import { ReaderService } from 'src/app/_services/reader.service'; import { SeriesService } from 'src/app/_services/series.service'; import { UploadService } from 'src/app/_services/upload.service'; @@ -81,6 +83,13 @@ export class CardDetailDrawerComponent implements OnInit { readingTime: HourEstimateRange = {maxHours: 1, minHours: 1, avgHours: 1, hasProgress: false}; minHoursToRead: number = 1; maxHoursToRead: number = 1; + /** + * We use a separate variable because if this is a volume, we need a sum of all chapters + */ + totalPages: number = 0; + + download$: Observable | null = null; + downloadInProgress: boolean = false; @@ -109,7 +118,7 @@ export class CardDetailDrawerComponent implements OnInit { private accountService: AccountService, private actionFactoryService: ActionFactoryService, private actionService: ActionService, private router: Router, private libraryService: LibraryService, private seriesService: SeriesService, private readerService: ReaderService, public metadataService: MetadataService, - public activeOffcanvas: NgbActiveOffcanvas) { } + public activeOffcanvas: NgbActiveOffcanvas, private downloadService: DownloadService) { } ngOnInit(): void { this.isChapter = this.utilityService.isChapter(this.data); @@ -123,13 +132,13 @@ export class CardDetailDrawerComponent implements OnInit { this.metadataService.getAgeRating(this.chapterMetadata.ageRating).subscribe(ageRating => this.ageRating = ageRating); - let totalPages = this.chapter.pages; + this.totalPages = this.chapter.pages; if (!this.isChapter) { // Need to account for multiple chapters if this is a volume - totalPages = this.utilityService.asVolume(this.data).chapters.map(c => c.pages).reduce((sum, d) => sum + d); + this.totalPages = this.utilityService.asVolume(this.data).chapters.map(c => c.pages).reduce((sum, d) => sum + d); } - this.readerService.getManualTimeToRead(this.chapterMetadata.wordCount, totalPages, this.chapter.files[0].format === MangaFormat.EPUB).subscribe((time) => this.readingTime = time); + this.readerService.getManualTimeToRead(this.chapterMetadata.wordCount, this.totalPages, this.chapter.files[0].format === MangaFormat.EPUB).subscribe((time) => this.readingTime = time); }); @@ -153,6 +162,7 @@ export class CardDetailDrawerComponent implements OnInit { }); this.chapterActions = this.actionFactoryService.getChapterActions(this.handleChapterActionCallback.bind(this)).filter(item => item.action !== Action.Edit); + this.chapterActions.push({title: 'Read', action: Action.Read, callback: this.handleChapterActionCallback.bind(this), requiresAdmin: false}); if (this.isChapter) { this.chapters.push(this.data as Chapter); @@ -245,22 +255,59 @@ export class CardDetailDrawerComponent implements OnInit { case(Action.AddToReadingList): this.actionService.addChapterToReadingList(chapter, this.seriesId); break; + case (Action.IncognitoRead): + this.readChapter(chapter, true); + break; + case (Action.Download): + this.download(chapter); + break; + case (Action.Read): + this.readChapter(chapter, false); + break; default: break; } } - readChapter(chapter: Chapter) { + readChapter(chapter: Chapter, incognito: boolean = false) { if (chapter.pages === 0) { this.toastr.error('There are no pages. Kavita was not able to read this archive.'); return; } + const params = this.readerService.getQueryParamsObject(incognito, false); if (chapter.files.length > 0 && chapter.files[0].format === MangaFormat.EPUB) { - this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'book', chapter.id]); + this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'book', chapter.id], {queryParams: params}); } else { - this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id]); + this.router.navigate(['library', this.libraryId, 'series', this.seriesId, 'manga', chapter.id], {queryParams: params}); } + this.close(); + } + + download(chapter: Chapter) { + if (this.downloadInProgress === true) { + this.toastr.info('Download is already in progress. Please wait.'); + return; + } + + this.downloadService.downloadChapterSize(chapter.id).pipe(take(1)).subscribe(async (size) => { + const wantToDownload = await this.downloadService.confirmSize(size, 'chapter'); + console.log('want to download: ', wantToDownload); + if (!wantToDownload) { return; } + this.downloadInProgress = true; + this.download$ = this.downloadService.downloadChapter(chapter).pipe( + tap(val => { + console.log(val); + }), + takeWhile(val => { + return val.state != 'DONE'; + }), + finalize(() => { + this.download$ = null; + this.downloadInProgress = false; + })); + this.download$.subscribe(() => {}); + }); } } diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html index a06f80af6..66b4c51d0 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.html @@ -12,18 +12,23 @@
  • - -
    -
    - +
    +
    + + +
    + +
    + +
    - - + +
    -
    +
    @@ -34,9 +39,52 @@ - + +
    + + + +
  • +
    + + + + of {{pagination.totalPages}} +
    +
  • +
    + +
    +
    + +
    +
    diff --git a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss index 50c6e4e18..005ce9057 100644 --- a/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss +++ b/UI/Web/src/app/cards/card-detail-layout/card-detail-layout.component.scss @@ -1,12 +1,23 @@ -.content-container { - display: inline-flex; +.viewport-container { + display: flex; + flex-direction: row; width: 100%; - height: calc((var(--vh) *100) - 152px); + height: calc((var(--vh) *100) - 162px); + margin-bottom: 10px; +} + +.content-container { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + margin-bottom: 10px; } .card-container { display: inline-block; width: 100%; + overflow-y: auto; } .grid { @@ -15,12 +26,17 @@ grid-gap: 0.5rem; justify-content: space-around; width: 100%; - max-height: calc((var(--vh) *100) - 152px); overflow-y: auto; overflow-x: hidden; align-items: start; } +@media (max-width: 576px) { + .grid { + grid-gap: 0.3rem; + } +} + .jump-bar { display: flex; flex-flow: column; diff --git a/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.html b/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.html index 2509711cb..504366f5d 100644 --- a/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.html +++ b/UI/Web/src/app/cards/card-item/card-actionables/card-actionables.component.html @@ -7,4 +7,5 @@
    + \ No newline at end of file diff --git a/UI/Web/src/app/manga-reader/manga-reader.component.html b/UI/Web/src/app/manga-reader/manga-reader.component.html index 655d2f9a7..f18ac2070 100644 --- a/UI/Web/src/app/manga-reader/manga-reader.component.html +++ b/UI/Web/src/app/manga-reader/manga-reader.component.html @@ -41,13 +41,13 @@
    -
    +
    -
    +
    diff --git a/UI/Web/src/app/manga-reader/manga-reader.component.scss b/UI/Web/src/app/manga-reader/manga-reader.component.scss index 681425de2..5ca1a0d1d 100644 --- a/UI/Web/src/app/manga-reader/manga-reader.component.scss +++ b/UI/Web/src/app/manga-reader/manga-reader.component.scss @@ -249,7 +249,7 @@ img { } $pagination-bg: rgba(0, 0, 0, 0); - //$pagination-bg: rgba(0, 0, 255, 0.4); + //$pagination-bg: rgba(0, 0, 255, 0.4); // DEBUG CODE .pagination-area { cursor: pointer; diff --git a/UI/Web/src/app/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/manga-reader.component.ts index b128b2331..9ddc1aa4c 100644 --- a/UI/Web/src/app/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/manga-reader.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Inject, OnDestroy, OnInit, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; import { DOCUMENT, Location } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; -import { take, takeUntil } from 'rxjs/operators'; +import { debounceTime, take, takeUntil } from 'rxjs/operators'; import { User } from '../_models/user'; import { AccountService } from '../_services/account.service'; import { ReaderService } from '../_services/reader.service'; @@ -10,7 +10,7 @@ import { NavService } from '../_services/nav.service'; import { ReadingDirection } from '../_models/preferences/reading-direction'; import { ScalingOption } from '../_models/preferences/scaling-option'; import { PageSplitOption } from '../_models/preferences/page-split-option'; -import { BehaviorSubject, forkJoin, ReplaySubject, Subject } from 'rxjs'; +import { BehaviorSubject, forkJoin, fromEvent, ReplaySubject, Subject } from 'rxjs'; import { ToastrService } from 'ngx-toastr'; import { Breakpoint, KEY_CODES, UtilityService } from '../shared/_services/utility.service'; import { CircularArray } from '../shared/data-structures/circular-array'; @@ -260,6 +260,11 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { */ backgroundColor: string = '#FFFFFF'; + /** + * This is here as absolute layout requires us to calculate a negative right property for the right pagination when there is overflow. This is calculated on scroll. + */ + rightPaginationOffset = 0; + getPageUrl = (pageNum: number) => { if (this.bookmarkMode) return this.readerService.getBookmarkPageUrl(this.seriesId, this.user.apiKey, pageNum); return this.readerService.getPageUrl(this.chapterId, pageNum); @@ -303,6 +308,14 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { return this.image?.nativeElement.height + 'px'; } + + get RightPaginationOffset() { + if (this.readerMode === ReaderMode.LeftRight && this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.HEIGHT) { + return (this.readingArea?.nativeElement?.scrollLeft || 0) * -1; + } + return 0; + } + get splitIconClass() { if (this.isSplitLeftToRight()) { return 'left-side'; @@ -369,8 +382,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { return; } - - this.libraryId = parseInt(libraryId, 10); this.seriesId = parseInt(seriesId, 10); this.chapterId = parseInt(chapterId, 10); @@ -449,12 +460,23 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } ngAfterViewInit() { - if (!this.canvas) { - return; - } - this.ctx = this.canvas.nativeElement.getContext('2d', { alpha: false }); - this.canvasImage.onload = () => this.renderPage(); + + fromEvent(this.readingArea.nativeElement, 'scroll').pipe(debounceTime(20), takeUntil(this.onDestroy)).subscribe(evt => { + if (this.readerMode === ReaderMode.Webtoon) return; + if (this.readerMode === ReaderMode.LeftRight && this.generalSettingsForm.get('fittingOption')?.value === FITTING_OPTION.HEIGHT) { + this.rightPaginationOffset = (this.readingArea.nativeElement.scrollLeft) * -1; + return; + } + this.rightPaginationOffset = 0; + }); this.getWindowDimensions(); + + if (this.canvas) { + this.ctx = this.canvas.nativeElement.getContext('2d', { alpha: false }); + this.canvasImage.onload = () => this.renderPage(); + } + + } ngOnDestroy() { @@ -562,7 +584,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { }); return; - } forkJoin({ diff --git a/UI/Web/src/app/manga-reader/manga-reader.module.ts b/UI/Web/src/app/manga-reader/manga-reader.module.ts index 0d15ebe03..2ac83713e 100644 --- a/UI/Web/src/app/manga-reader/manga-reader.module.ts +++ b/UI/Web/src/app/manga-reader/manga-reader.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MangaReaderComponent } from './manga-reader.component'; import { ReactiveFormsModule } from '@angular/forms'; -import { NgbButtonsModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; import { MangaReaderRoutingModule } from './manga-reader.router.module'; import { SharedModule } from '../shared/shared.module'; import { NgxSliderModule } from '@angular-slider/ngx-slider'; @@ -19,7 +19,6 @@ import { ReaderSharedModule } from '../reader-shared/reader-shared.module'; MangaReaderRoutingModule, ReactiveFormsModule, - NgbButtonsModule, NgbDropdownModule, NgxSliderModule, SharedModule, diff --git a/UI/Web/src/app/series-detail/series-detail.component.html b/UI/Web/src/app/series-detail/series-detail.component.html index d23ca893b..9770a2dad 100644 --- a/UI/Web/src/app/series-detail/series-detail.component.html +++ b/UI/Web/src/app/series-detail/series-detail.component.html @@ -73,12 +73,12 @@
    @@ -90,7 +90,7 @@
    @@ -105,7 +105,7 @@
    diff --git a/UI/Web/src/app/series-detail/series-detail.component.ts b/UI/Web/src/app/series-detail/series-detail.component.ts index 6cde05998..f3ba69ae5 100644 --- a/UI/Web/src/app/series-detail/series-detail.component.ts +++ b/UI/Web/src/app/series-detail/series-detail.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { NgbModal, NgbNavChangeEvent, NgbOffcanvas } from '@ng-bootstrap/ng-bootstrap'; import { ToastrService } from 'ngx-toastr'; import { forkJoin, Subject } from 'rxjs'; -import { finalize, take, takeUntil, takeWhile } from 'rxjs/operators'; +import { finalize, mergeMap, take, takeUntil, takeWhile } from 'rxjs/operators'; import { BulkSelectionService } from '../cards/bulk-selection.service'; import { CardDetailsModalComponent } from '../cards/_modals/card-details-modal/card-details-modal.component'; import { EditSeriesModalComponent } from '../cards/_modals/edit-series-modal/edit-series-modal.component'; @@ -96,11 +96,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { seriesImage: string = ''; downloadInProgress: boolean = false; - /** - * Tricks the cover images for volume/chapter cards to update after we update one of them - */ - coverImageOffset: number = 0; - /** * If an action is currently being done, don't let the user kick off another action */ @@ -357,8 +352,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { } loadSeries(seriesId: number) { - this.coverImageOffset = 0; - this.seriesService.getMetadata(seriesId).subscribe(metadata => this.seriesMetadata = metadata); this.readingListService.getReadingListsForSeries(seriesId).subscribe(lists => { this.readingLists = lists; @@ -567,11 +560,6 @@ export class SeriesDetailComponent implements OnInit, OnDestroy { drawerRef.componentInstance.parentName = this.series?.name; drawerRef.componentInstance.seriesId = this.series?.id; drawerRef.componentInstance.libraryId = this.series?.libraryId; - drawerRef.closed.subscribe((result: {coverImageUpdate: boolean}) => { - if (result.coverImageUpdate) { - this.coverImageOffset += 1; - } - }); } openEditSeriesModal() { diff --git a/UI/Web/src/app/series-detail/series-metadata-detail/series-metadata-detail.component.html b/UI/Web/src/app/series-detail/series-metadata-detail/series-metadata-detail.component.html index cccd28348..a321d6140 100644 --- a/UI/Web/src/app/series-detail/series-metadata-detail/series-metadata-detail.component.html +++ b/UI/Web/src/app/series-detail/series-metadata-detail/series-metadata-detail.component.html @@ -6,7 +6,7 @@
    - + {{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}
    @@ -15,7 +15,7 @@
    - + {{seriesMetadata.releaseYear}}
    @@ -24,7 +24,7 @@
    - + {{seriesMetadata.language | defaultValue:'en' | languageName | async}}
    @@ -33,16 +33,18 @@
    - - {{seriesMetadata.publicationStatus | publicationStatus}} - + + + {{pubStatus}} + +
    - + {{utilityService.mangaFormat(series.format)}}
    @@ -51,7 +53,7 @@
    - + {{series.latestReadDate | date:'shortDate'}}
    @@ -62,7 +64,7 @@
    - + {{series.wordCount | compactNumber}} Words
    @@ -72,7 +74,7 @@
    - + {{series.pages | number:''}} Pages
    @@ -83,7 +85,7 @@
    - + {{readingTime.minHours}}{{readingTime.maxHours !== readingTime.minHours ? ('-' + readingTime.maxHours) : ''}} Hour{{readingTime.minHours > 1 ? 's' : ''}}
    @@ -93,7 +95,7 @@
    - + ~{{readingTimeLeft.avgHours}} Hour{{readingTimeLeft.avgHours > 1 ? 's' : ''}} Left
    diff --git a/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.html b/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.html index 064f88f33..c3308b8a0 100644 --- a/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.html +++ b/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.html @@ -1,5 +1,8 @@
    +
    + {{label}} +
    diff --git a/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.scss b/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.scss index 44b8535c7..3c5193f1d 100644 --- a/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.scss +++ b/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.scss @@ -12,4 +12,10 @@ .text { padding-top: 5px; text-align: center; +} + +.label { + padding-bottom: 5px; + text-align: center; + font-weight: bold; } \ No newline at end of file diff --git a/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.ts b/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.ts index fe5be4b32..0d63e08d8 100644 --- a/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.ts +++ b/UI/Web/src/app/shared/icon-and-title/icon-and-title.component.ts @@ -11,6 +11,7 @@ export class IconAndTitleComponent implements OnInit { */ @Input() clickable: boolean = true; @Input() title: string = ''; + @Input() label: string = ''; /** * Font classes used to display font */ diff --git a/UI/Web/src/app/sidenav/side-nav/side-nav.component.html b/UI/Web/src/app/sidenav/side-nav/side-nav.component.html index e0f66775c..775f4c73d 100644 --- a/UI/Web/src/app/sidenav/side-nav/side-nav.component.html +++ b/UI/Web/src/app/sidenav/side-nav/side-nav.component.html @@ -12,11 +12,11 @@ -
    +
    -
    +
    - +