diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index 1eb0dbb74..38922aad9 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -308,7 +308,7 @@ public class OpdsController : BaseApiController var userId = await GetUser(apiKey); if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds) return BadRequest(await _localizationService.Translate(userId, "opds-disabled")); - var (baseUrl, prefix) = await GetPrefix(); + var (_, prefix) = await GetPrefix(); var filters = _unitOfWork.AppUserSmartFilterRepository.GetAllDtosByUserId(userId); var feed = CreateFeed(await _localizationService.Translate(userId, "smartFilters"), $"{prefix}{apiKey}/smart-filters", apiKey, prefix); @@ -337,7 +337,7 @@ public class OpdsController : BaseApiController var userId = await GetUser(apiKey); if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds) return BadRequest(await _localizationService.Translate(userId, "opds-disabled")); - var (baseUrl, prefix) = await GetPrefix(); + var (_, prefix) = await GetPrefix(); var externalSources = await _unitOfWork.AppUserExternalSourceRepository.GetExternalSources(userId); var feed = CreateFeed(await _localizationService.Translate(userId, "external-sources"), $"{prefix}{apiKey}/external-sources", apiKey, prefix); @@ -370,15 +370,13 @@ public class OpdsController : BaseApiController if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds) return BadRequest(await _localizationService.Translate(userId, "opds-disabled")); var (baseUrl, prefix) = await GetPrefix(); - var libraries = await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId); var feed = CreateFeed(await _localizationService.Translate(userId, "libraries"), $"{prefix}{apiKey}/libraries", apiKey, prefix); SetFeedId(feed, "libraries"); // Ensure libraries follow SideNav order var userSideNavStreams = await _unitOfWork.UserRepository.GetSideNavStreams(userId, false); - foreach (var sideNavStream in userSideNavStreams.Where(s => s.StreamType == SideNavStreamType.Library)) + foreach (var library in userSideNavStreams.Where(s => s.StreamType == SideNavStreamType.Library).Select(sideNavStream => sideNavStream.Library)) { - var library = sideNavStream.Library; feed.Entries.Add(new FeedEntry() { Id = library!.Id.ToString(), @@ -779,13 +777,13 @@ public class OpdsController : BaseApiController var chapters = (await _unitOfWork.ChapterRepository.GetChaptersAsync(volume.Id)).OrderBy(x => double.Parse(x.Number, CultureInfo.InvariantCulture), _chapterSortComparer); - foreach (var chapter in chapters) + foreach (var chapterId in chapters.Select(c => c.Id)) { - var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapter.Id); - var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id); + var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId); + var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId); foreach (var mangaFile in files) { - feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl)); + feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volume.Id, chapterId, mangaFile, series, chapterTest, apiKey, prefix, baseUrl)); } } diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index 932fba045..dcee9b62d 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -105,18 +105,23 @@ public class ReaderController : BaseApiController /// Should Kavita extract pdf into images. Defaults to false. /// [HttpGet("image")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId","page", "extractPdf", "apiKey"})] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId", "page", "extractPdf", "apiKey"})] [AllowAnonymous] public async Task GetImage(int chapterId, int page, string apiKey, bool extractPdf = false) { if (page < 0) page = 0; var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey); if (userId == 0) return BadRequest(); - var chapter = await _cacheService.Ensure(chapterId, extractPdf); - if (chapter == null) return NoContent(); try { + if (new Random().Next(1, 10) > 5) + { + await Task.Delay(1000); + } + var chapter = await _cacheService.Ensure(chapterId, extractPdf); + if (chapter == null) return NoContent(); + _logger.LogInformation("Fetching Page {PageNum} on Chapter {ChapterId}", page, chapterId); var path = _cacheService.GetCachedPagePath(chapter.Id, page); if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest(await _localizationService.Translate(userId, "no-image-for-page", page)); @@ -245,8 +250,8 @@ public class ReaderController : BaseApiController LibraryId = dto.LibraryId, IsSpecial = dto.IsSpecial, Pages = dto.Pages, - SeriesTotalPages = series?.Pages ?? 0, - SeriesTotalPagesRead = series?.PagesRead ?? 0, + SeriesTotalPages = series.Pages, + SeriesTotalPagesRead = series.PagesRead, ChapterTitle = dto.ChapterTitle ?? string.Empty, Subtitle = string.Empty, Title = dto.SeriesName, diff --git a/API/Data/Repositories/LibraryRepository.cs b/API/Data/Repositories/LibraryRepository.cs index 6cae8e2d3..7c61bc890 100644 --- a/API/Data/Repositories/LibraryRepository.cs +++ b/API/Data/Repositories/LibraryRepository.cs @@ -280,7 +280,7 @@ public class LibraryRepository : ILibraryRepository { Title = s, IsoCode = s - };; + }; } public IEnumerable GetAllPublicationStatusesDtosForLibrariesAsync(List libraryIds) diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index ae83ac789..67464bad3 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -644,6 +644,7 @@ public class DirectoryService : IDirectoryService /// Scans a directory by utilizing a recursive folder search. If a .kavitaignore file is found, will ignore matching patterns /// /// + /// /// /// public IList ScanFiles(string folderPath, string supportedExtensions, GlobMatcher? matcher = null) diff --git a/API/Services/ReaderService.cs b/API/Services/ReaderService.cs index a56981069..052bf87f2 100644 --- a/API/Services/ReaderService.cs +++ b/API/Services/ReaderService.cs @@ -398,7 +398,7 @@ public class ReaderService : IReaderService // Handle Chapters within next Volume // ! When selecting the chapter for the next volume, we need to make sure a c0 comes before a c1+ var chapters = volume.Chapters.OrderBy(x => x.Number.AsDouble(), _chapterSortComparer).ToList(); - if (currentChapter.Number.Equals(Parser.DefaultChapter) && chapters.Last().Number.Equals(Parser.DefaultChapter)) + if (currentChapter.Number.Equals(Parser.DefaultChapter) && chapters[^1].Number.Equals(Parser.DefaultChapter)) { // We need to handle an extra check if the current chapter is the last special, as we should return -1 if (currentChapter.IsSpecial) return -1; diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index a234f64a2..b5d41ac52 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -813,7 +813,7 @@ public class SeriesService : ISeriesService private static double ExponentialSmoothing(IList data, double alpha) { - var forecast = data.First(); + var forecast = data[0]; foreach (var value in data) { diff --git a/API/Services/StreamService.cs b/API/Services/StreamService.cs index fc1037823..f12f10a8a 100644 --- a/API/Services/StreamService.cs +++ b/API/Services/StreamService.cs @@ -71,7 +71,7 @@ public class StreamService : IStreamService var smartFilter = await _unitOfWork.AppUserSmartFilterRepository.GetById(smartFilterId); if (smartFilter == null) throw new KavitaException(await _localizationService.Translate(userId, "smart-filter-doesnt-exist")); - var stream = user?.DashboardStreams.FirstOrDefault(d => d.SmartFilter?.Id == smartFilterId); + var stream = user.DashboardStreams.FirstOrDefault(d => d.SmartFilter?.Id == smartFilterId); if (stream != null) throw new KavitaException(await _localizationService.Translate(userId, "smart-filter-already-in-use")); var maxOrder = user!.DashboardStreams.Max(d => d.Order); @@ -159,7 +159,7 @@ public class StreamService : IStreamService var smartFilter = await _unitOfWork.AppUserSmartFilterRepository.GetById(smartFilterId); if (smartFilter == null) throw new KavitaException(await _localizationService.Translate(userId, "smart-filter-doesnt-exist")); - var stream = user?.SideNavStreams.FirstOrDefault(d => d.SmartFilter?.Id == smartFilterId); + var stream = user.SideNavStreams.FirstOrDefault(d => d.SmartFilter?.Id == smartFilterId); if (stream != null) throw new KavitaException(await _localizationService.Translate(userId, "smart-filter-already-in-use")); var maxOrder = user!.SideNavStreams.Max(d => d.Order); diff --git a/API/Services/TachiyomiService.cs b/API/Services/TachiyomiService.cs index 3494f0bf6..e4e9aab21 100644 --- a/API/Services/TachiyomiService.cs +++ b/API/Services/TachiyomiService.cs @@ -72,7 +72,7 @@ public class TachiyomiService : ITachiyomiService if (looseLeafChapterVolume == null) { var volumeChapter = _mapper.Map(volumes - .Last().Chapters + [^1].Chapters .OrderBy(c => c.Number.AsFloat(), ChapterSortComparerZeroFirst.Default) .Last()); if (volumeChapter.Number == "0") diff --git a/API/Services/Tasks/Scanner/LibraryWatcher.cs b/API/Services/Tasks/Scanner/LibraryWatcher.cs index 2cbb24fb4..00dbe135c 100644 --- a/API/Services/Tasks/Scanner/LibraryWatcher.cs +++ b/API/Services/Tasks/Scanner/LibraryWatcher.cs @@ -179,7 +179,7 @@ public class LibraryWatcher : ILibraryWatcher /// private void OnError(object sender, ErrorEventArgs e) { - _logger.LogError(e.GetException(), "[LibraryWatcher] An error occured, likely too many changes occured at once or the folder being watched was deleted. Restarting Watchers"); + _logger.LogError(e.GetException(), "[LibraryWatcher] An error occured, likely too many changes occured at once or the folder being watched was deleted. Restarting Watchers {Current}/{Total}", _bufferFullCounter, 3); bool condition; lock (Lock) { diff --git a/API/Services/Tasks/Scanner/ParseScannedFiles.cs b/API/Services/Tasks/Scanner/ParseScannedFiles.cs index a7e64bd6a..585a60073 100644 --- a/API/Services/Tasks/Scanner/ParseScannedFiles.cs +++ b/API/Services/Tasks/Scanner/ParseScannedFiles.cs @@ -312,7 +312,6 @@ public class ParseScannedFiles } await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.FileScanProgressEvent("File Scan Done", library.Name, ProgressEventType.Ended)); - return; async Task ProcessFolder(IList files, string folder) { diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index d73f3e545..cb9ae2916 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -295,7 +295,7 @@ public class ProcessSeries : IProcessSeries if (series.Format == MangaFormat.Epub || series.Format == MangaFormat.Pdf && chapters.Count == 1) { series.Metadata.MaxCount = 1; - } else if (series.Metadata.TotalCount == 1 && chapters.Count == 1 && chapters.First().IsSpecial) + } else if (series.Metadata.TotalCount == 1 && chapters.Count == 1 && chapters[0].IsSpecial) { // If a series has a TotalCount of 1 and there is only a Special, mark it as Complete series.Metadata.MaxCount = series.Metadata.TotalCount; diff --git a/API/Startup.cs b/API/Startup.cs index c44f6a5d6..8cf120789 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -347,6 +347,7 @@ public class Startup => { opts.EnrichDiagnosticContext = LogEnricher.EnrichFromRequest; + opts.IncludeQueryInRequestPath = true; }); app.Use(async (context, next) => diff --git a/UI/Web/src/app/_models/series-detail/relation-kind.ts b/UI/Web/src/app/_models/series-detail/relation-kind.ts index 77470041c..2de8e701f 100644 --- a/UI/Web/src/app/_models/series-detail/relation-kind.ts +++ b/UI/Web/src/app/_models/series-detail/relation-kind.ts @@ -17,7 +17,7 @@ export enum RelationKind { Edition = 13 } -export const RelationKinds = [ +const RelationKindsUnsorted = [ {text: 'Prequel', value: RelationKind.Prequel}, {text: 'Sequel', value: RelationKind.Sequel}, {text: 'Spin Off', value: RelationKind.SpinOff}, @@ -31,3 +31,5 @@ export const RelationKinds = [ {text: 'Doujinshi', value: RelationKind.Doujinshi}, {text: 'Other', value: RelationKind.Other}, ]; + +export const RelationKinds = RelationKindsUnsorted.slice().sort((a, b) => a.text.localeCompare(b.text)); diff --git a/UI/Web/src/app/book-reader/_components/book-line-overlay/book-line-overlay.component.ts b/UI/Web/src/app/book-reader/_components/book-line-overlay/book-line-overlay.component.ts index d5cc47499..772258cce 100644 --- a/UI/Web/src/app/book-reader/_components/book-line-overlay/book-line-overlay.component.ts +++ b/UI/Web/src/app/book-reader/_components/book-line-overlay/book-line-overlay.component.ts @@ -38,6 +38,7 @@ export class BookLineOverlayComponent implements OnInit { @Input({required: true}) pageNumber: number = 0; @Input({required: true}) parent: ElementRef | undefined; @Output() refreshToC: EventEmitter = new EventEmitter(); + @Output() isOpen: EventEmitter = new EventEmitter(false); xPath: string = ''; selectedText: string = ''; @@ -84,6 +85,8 @@ export class BookLineOverlayComponent implements OnInit { if (!event.target) return; if ((!selection || selection.toString().trim() === '' || selection.toString().trim() === this.selectedText)) { + event.preventDefault(); + event.stopPropagation(); this.reset(); return; } @@ -96,6 +99,7 @@ export class BookLineOverlayComponent implements OnInit { this.xPath = '//' + this.xPath; } + this.isOpen.emit(true); event.preventDefault(); event.stopPropagation(); } @@ -137,6 +141,7 @@ export class BookLineOverlayComponent implements OnInit { if (selection) { selection.removeAllRanges(); } + this.isOpen.emit(false); this.cdRef.markForCheck(); } diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html index ccdc2aa2d..38b5e9bb4 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.html @@ -3,13 +3,14 @@
{{t('skip-header')}} - + @@ -18,11 +19,6 @@ {{t('close-reader')}}
- - - - -
@@ -129,13 +125,14 @@
- +
- +
-
- + @if(isLoading) {
{{t('loading-book')}}
-
- - + } @else { + ({{t('incognito-mode-label')}}) - {{bookTitle}} - + {{bookTitle}} + }
diff --git a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts index c4cf2cdec..b18569e86 100644 --- a/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/_components/book-reader/book-reader.component.ts @@ -185,6 +185,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { * Belongs to drawer component */ drawerOpen = false; + /** + * If the word/line overlay is open + */ + isLineOverlayOpen = false; /** * If the action bar is visible */ @@ -1630,20 +1634,21 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.cdRef.markForCheck(); } - // Responsible for handling pagination only handleContainerClick(event: MouseEvent) { - if (this.drawerOpen || ['action-bar', 'offcanvas-backdrop'].some(className => (event.target as Element).classList.contains(className))) { + if (this.drawerOpen || this.isLineOverlayOpen || ['action-bar', 'offcanvas-backdrop'].some(className => (event.target as Element).classList.contains(className))) { return; } - if (this.isCursorOverLeftPaginationArea(event)) { - this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD); - } else if (this.isCursorOverRightPaginationArea(event)) { - this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS) - } else { - this.toggleMenu(event); + if (this.clickToPaginate) { + if (this.isCursorOverLeftPaginationArea(event)) { + this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD); + } else if (this.isCursorOverRightPaginationArea(event)) { + this.movePage(this.readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS) + } } + + this.toggleMenu(event); } handleReaderClick(event: MouseEvent) { @@ -1706,4 +1711,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { refreshPersonalToC() { this.refreshPToC.emit(); } + + updateLineOverlayOpen(isOpen: boolean) { + // HACK: This hack allows the boolean to be changed to false so that the pagination doesn't trigger and move us to the next page when + // the book overlay is just closing + setTimeout(() => { + this.isLineOverlayOpen = isOpen; + this.cdRef.markForCheck(); + }, 10); + } } diff --git a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html index 0a66a93be..3a3b33907 100644 --- a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html +++ b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.html @@ -106,9 +106,9 @@
- + (newItemAdded)="metadata.genresLocked = true"> {{item.title}} @@ -142,9 +142,9 @@
- + (newItemAdded)="metadata.languageLocked = true"> {{item.title}} @@ -159,7 +159,7 @@
-
@@ -170,7 +170,7 @@
-
@@ -186,9 +186,9 @@
- + (newItemAdded)="metadata.writerLocked = true"> {{item.name}} @@ -201,9 +201,9 @@
- + (newItemAdded)="metadata.coverArtistLocked = true"> {{item.name}} @@ -218,9 +218,9 @@
- + (newItemAdded)="metadata.publisherLocked = true"> {{item.name}} @@ -233,9 +233,9 @@
- + (newItemAdded)="metadata.pencillerLocked = true"> {{item.name}} @@ -250,9 +250,9 @@
- + (newItemAdded)="metadata.lettererLocked = true"> {{item.name}} @@ -265,9 +265,9 @@
- + (newItemAdded)="metadata.inkerLocked = true"> {{item.name}} @@ -283,9 +283,9 @@
- + (newItemAdded)="metadata.editorLocked = true"> {{item.name}} @@ -298,9 +298,9 @@
- + (newItemAdded)="metadata.coloristLocked = true"> {{item.name}} @@ -316,9 +316,9 @@
- + (newItemAdded)="metadata.characterLocked = true"> {{item.name}} @@ -331,9 +331,9 @@
- + (newItemAdded)="metadata.translatorLocked = true"> {{item.name}} diff --git a/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.ts b/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.ts index 78aae0945..df5eac4ce 100644 --- a/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.ts +++ b/UI/Web/src/app/cards/edit-series-relation/edit-series-relation.component.ts @@ -47,6 +47,15 @@ interface RelationControl { }) export class EditSeriesRelationComponent implements OnInit { + private readonly destroyRef = inject(DestroyRef); + private readonly cdRef = inject(ChangeDetectorRef); + private readonly seriesService = inject(SeriesService); + private readonly utilityService = inject(UtilityService); + private readonly libraryService = inject(LibraryService); + private readonly searchService = inject(SearchService); + public readonly imageService = inject(ImageService); + protected readonly RelationKind = RelationKind; + @Input({required: true}) series!: Series; /** * This will tell the component to save based on its internal state @@ -60,16 +69,6 @@ export class EditSeriesRelationComponent implements OnInit { libraryNames: {[key:number]: string} = {}; focusTypeahead = new EventEmitter(); - private readonly destroyRef = inject(DestroyRef); - - get RelationKind() { - return RelationKind; - } - - - constructor(private seriesService: SeriesService, private utilityService: UtilityService, - public imageService: ImageService, private libraryService: LibraryService, private searchService: SearchService, - private readonly cdRef: ChangeDetectorRef) {} ngOnInit(): void { this.seriesService.getRelatedForSeries(this.series.id).subscribe(async relations => { diff --git a/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts b/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts index 04c0a7957..8a2f04fc0 100644 --- a/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts +++ b/UI/Web/src/app/manga-reader/_components/double-renderer/double-renderer.component.ts @@ -76,9 +76,8 @@ export class DoubleRendererComponent implements OnInit, ImageRenderer { shouldRenderDouble$!: Observable; - get ReaderMode() {return ReaderMode;} - get LayoutMode() {return LayoutMode;} - + protected readonly ReaderMode = ReaderMode; + protected readonly LayoutMode = LayoutMode; constructor(private readonly cdRef: ChangeDetectorRef, public mangaReaderService: ManagaReaderService, diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html index 5dd6da6fb..29fd55940 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.html @@ -19,7 +19,7 @@
{{title}} ({{t('incognito-title')}})
- {{subtitle}} {{t('series-progress', {percentage: (((totalSeriesPagesRead + pageNum) / totalSeriesPages) | percent)}) }} + {{subtitle}} {{t('series-progress', {percentage: (Math.min(1, ((totalSeriesPagesRead + pageNum) / totalSeriesPages)) | percent)}) }}
@@ -36,7 +36,7 @@
- +
diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts index 8fd798f91..596f06291 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts @@ -13,7 +13,7 @@ import { OnInit, ViewChild } from '@angular/core'; -import {DOCUMENT, NgStyle, NgIf, NgFor, NgSwitch, NgSwitchCase, PercentPipe, NgClass} from '@angular/common'; +import {DOCUMENT, NgStyle, NgIf, NgFor, NgSwitch, NgSwitchCase, PercentPipe, NgClass, AsyncPipe} from '@angular/common'; import {ActivatedRoute, Router} from '@angular/router'; import { BehaviorSubject, @@ -68,6 +68,7 @@ import { InfiniteScrollerComponent } from '../infinite-scroller/infinite-scrolle import { SwipeDirective } from '../../../ng-swipe/ng-swipe.directive'; import { LoadingComponent } from '../../../shared/loading/loading.component'; import {translate, TranslocoDirective} from "@ngneat/transloco"; +import {shareReplay} from "rxjs/operators"; const PREFETCH_PAGES = 10; @@ -124,7 +125,7 @@ enum KeyDirection { imports: [NgStyle, NgIf, LoadingComponent, SwipeDirective, CanvasRendererComponent, SingleRendererComponent, DoubleRendererComponent, DoubleReverseRendererComponent, DoubleNoCoverRendererComponent, InfiniteScrollerComponent, NgxSliderModule, ReactiveFormsModule, NgFor, NgSwitch, NgSwitchCase, FittingIconPipe, ReaderModeIconPipe, - FullscreenIconPipe, TranslocoDirective, NgbProgressbar, PercentPipe, NgClass] + FullscreenIconPipe, TranslocoDirective, NgbProgressbar, PercentPipe, NgClass, AsyncPipe] }) export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { @@ -158,6 +159,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { protected readonly LayoutMode = LayoutMode; protected readonly ReadingDirection = ReadingDirection; protected readonly Breakpoint = Breakpoint; + protected readonly Math = Math; libraryId!: number; seriesId!: number; @@ -400,7 +402,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { // Renderer interaction readerSettings$!: Observable; private currentImage: Subject = new ReplaySubject(1); - currentImage$: Observable = this.currentImage.asObservable(); + currentImage$: Observable = this.currentImage.asObservable().pipe( + shareReplay({refCount: true, bufferSize: 2}) + ); private pageNumSubject: Subject<{pageNum: number, maxPages: number}> = new ReplaySubject(); pageNum$: Observable<{pageNum: number, maxPages: number}> = this.pageNumSubject.asObservable(); @@ -743,18 +747,34 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { * @param pageNum Page Number to load * @param forceNew Forces to fetch a new image * @param chapterId ChapterId to fetch page from. Defaults to current chapterId. Not used when in bookmark mode - * @returns + * @returns HTMLImageElement | undefined */ getPage(pageNum: number, chapterId: number = this.chapterId, forceNew: boolean = false) { - let img; + let img: HTMLImageElement | undefined; if (this.bookmarkMode) img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum); else img = this.cachedImages.find(img => this.readerService.imageUrlToPageNum(img.src) === pageNum && (this.readerService.imageUrlToChapterId(img.src) == chapterId || this.readerService.imageUrlToChapterId(img.src) === -1) ); + + //console.log('Requesting page ', pageNum, ' found page: ', img, ' and app is requesting new image? ', forceNew); if (!img || forceNew) { img = new Image(); img.src = this.getPageUrl(pageNum, chapterId); + img.onload = (evt) => { + this.currentImage.next(img!); + this.cdRef.markForCheck(); + } + // img.onerror = (evt) => { + // const event = evt as Event; + // const page = this.readerService.imageUrlToPageNum((event.target as HTMLImageElement).src); + // console.error('Image failed to load: ', page); + // (event.target as HTMLImageElement).onerror = null; + // const newSrc = this.getPageUrl(pageNum, chapterId) + '#' + new Date().getTime(); + // console.log('requesting page ', page, ' with url: ', newSrc); + // (event.target as HTMLImageElement).src = newSrc; + // this.cdRef.markForCheck(); + // } } return img; @@ -1224,6 +1244,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { */ setCanvasImage() { if (this.cachedImages === undefined) return; + this.canvasImage = this.getPage(this.pageNum, this.chapterId, this.layoutMode !== LayoutMode.Single); if (!this.canvasImage.complete) { this.canvasImage.addEventListener('load', () => { @@ -1346,19 +1367,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { const index = (numOffset % this.cachedImages.length + this.cachedImages.length) % this.cachedImages.length; const cachedImagePageNum = this.readerService.imageUrlToPageNum(this.cachedImages[index].src); if (cachedImagePageNum !== numOffset) { - this.cachedImages[index] = new Image(); - this.cachedImages[index].src = this.getPageUrl(numOffset); - this.cachedImages[index].onload = (evt) => { - this.cdRef.markForCheck(); - } + this.cachedImages[index] = this.getPage(numOffset, this.chapterId); } } - - //const pages = this.cachedImages.map(img => [this.readerService.imageUrlToChapterId(img.src), this.readerService.imageUrlToPageNum(img.src)]); - // console.log(this.pageNum, ' Prefetched pages: ', pages.map(p => { - // if (this.pageNum === p[1]) return '[' + p + ']'; - // return '' + p - // })); } @@ -1681,4 +1692,5 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { d.text = translate('preferences.' + o.text); return d; } + } diff --git a/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.html b/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.html index de7eca3e0..8bf38e878 100644 --- a/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.html +++ b/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.html @@ -1,12 +1,12 @@ - -
- -  - -
-
+@if(isValid() && !mangaReaderService.shouldSplit(currentImage, pageSplit)) { +
+ @if(currentImage) { +  + } +
+} diff --git a/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts b/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts index c1ac8eff2..912877f4e 100644 --- a/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts +++ b/UI/Web/src/app/manga-reader/_components/single-renderer/single-renderer.component.ts @@ -27,7 +27,7 @@ import { SafeStylePipe } from '../../../_pipes/safe-style.pipe'; styleUrls: ['./single-renderer.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [NgIf, AsyncPipe, SafeStylePipe] + imports: [AsyncPipe, SafeStylePipe] }) export class SingleRendererComponent implements OnInit, ImageRenderer { @@ -137,7 +137,7 @@ export class SingleRendererComponent implements OnInit, ImageRenderer { return fit; }), - shareReplay(), + shareReplay({refCount: true, bufferSize: 1}), filter(_ => this.isValid()), takeUntilDestroyed(this.destroyRef), ); diff --git a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts index 9695465ea..e9de2a4b2 100644 --- a/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts +++ b/UI/Web/src/app/sidenav/_modals/library-settings-modal/library-settings-modal.component.ts @@ -153,7 +153,8 @@ export class LibrarySettingsModalComponent implements OnInit { // This needs to only apply after first render this.libraryForm.get('type')?.valueChanges.pipe( tap((type: LibraryType) => { - switch (type) { + const libType = parseInt(type + '', 10) as LibraryType; + switch (libType) { case LibraryType.Manga: this.libraryForm.get(FileTypeGroup.Archive + '')?.setValue(true); this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(true); @@ -177,6 +178,7 @@ export class LibrarySettingsModalComponent implements OnInit { this.libraryForm.get(FileTypeGroup.Images + '')?.setValue(true); this.libraryForm.get(FileTypeGroup.Pdf + '')?.setValue(false); this.libraryForm.get(FileTypeGroup.Epub + '')?.setValue(false); + break; } }), takeUntilDestroyed(this.destroyRef) @@ -203,15 +205,17 @@ export class LibrarySettingsModalComponent implements OnInit { for(let glob of this.library.excludePatterns) { this.libraryForm.addControl('excludeGlob-' , new FormControl(glob, [])); } + this.excludePatterns = this.library.excludePatterns; } else { for(let fileTypeGroup of allFileTypeGroup) { this.libraryForm.addControl(fileTypeGroup + '', new FormControl(true, [])); } } - this.excludePatterns = this.library.excludePatterns; + if (this.excludePatterns.length === 0) { this.excludePatterns = ['']; } + this.cdRef.markForCheck(); }