Skip to content

Commit

Permalink
Reader Polish (#2465)
Browse files Browse the repository at this point in the history
Co-authored-by: Andre Smith <[email protected]>
  • Loading branch information
majora2007 and Hobogrammer authored Nov 30, 2023
1 parent 9fdaf5f commit e489d24
Show file tree
Hide file tree
Showing 24 changed files with 156 additions and 120 deletions.
16 changes: 7 additions & 9 deletions API/Controllers/OPDSController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ public async Task<IActionResult> GetSmartFilters(string apiKey)
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);
Expand Down Expand Up @@ -337,7 +337,7 @@ public async Task<IActionResult> GetExternalSources(string apiKey)
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);
Expand Down Expand Up @@ -370,15 +370,13 @@ public async Task<IActionResult> GetLibraries(string apiKey)
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(),
Expand Down Expand Up @@ -779,13 +777,13 @@ public async Task<IActionResult> GetSeries(string apiKey, int seriesId)
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));
}
}

Expand Down
15 changes: 10 additions & 5 deletions API/Controllers/ReaderController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,23 @@ public async Task<ActionResult> GetPdf(int chapterId, string apiKey)
/// <param name="extractPdf">Should Kavita extract pdf into images. Defaults to false.</param>
/// <returns></returns>
[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<ActionResult> 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));
Expand Down Expand Up @@ -245,8 +250,8 @@ public async Task<ActionResult<ChapterInfoDto>> GetChapterInfo(int chapterId, bo
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,
Expand Down
2 changes: 1 addition & 1 deletion API/Data/Repositories/LibraryRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ private static LanguageDto GetCulture(string s)
{
Title = s,
IsoCode = s
};;
};
}

public IEnumerable<PublicationStatusDto> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds)
Expand Down
1 change: 1 addition & 0 deletions API/Services/DirectoryService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ public string GetParentDirectoryName(string fileOrFolder)
/// Scans a directory by utilizing a recursive folder search. If a .kavitaignore file is found, will ignore matching patterns
/// </summary>
/// <param name="folderPath"></param>
/// <param name="supportedExtensions"></param>
/// <param name="matcher"></param>
/// <returns></returns>
public IList<string> ScanFiles(string folderPath, string supportedExtensions, GlobMatcher? matcher = null)
Expand Down
2 changes: 1 addition & 1 deletion API/Services/ReaderService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ public async Task<int> GetNextChapterIdAsync(int seriesId, int volumeId, int cur
// 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;
Expand Down
2 changes: 1 addition & 1 deletion API/Services/SeriesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ public async Task<NextExpectedChapterDto> GetEstimatedChapterCreationDate(int se

private static double ExponentialSmoothing(IList<double> data, double alpha)
{
var forecast = data.First();
var forecast = data[0];

foreach (var value in data)
{
Expand Down
4 changes: 2 additions & 2 deletions API/Services/StreamService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public async Task<DashboardStreamDto> CreateDashboardStreamFromSmartFilter(int u
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);
Expand Down Expand Up @@ -159,7 +159,7 @@ public async Task<SideNavStreamDto> CreateSideNavStreamFromSmartFilter(int userI
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);
Expand Down
2 changes: 1 addition & 1 deletion API/Services/TachiyomiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public TachiyomiService(IUnitOfWork unitOfWork, IMapper mapper, ILogger<ReaderSe
if (looseLeafChapterVolume == null)
{
var volumeChapter = _mapper.Map<ChapterDto>(volumes
.Last().Chapters
[^1].Chapters
.OrderBy(c => c.Number.AsFloat(), ChapterSortComparerZeroFirst.Default)
.Last());
if (volumeChapter.Number == "0")
Expand Down
2 changes: 1 addition & 1 deletion API/Services/Tasks/Scanner/LibraryWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ private void OnDeleted(object sender, FileSystemEventArgs e) {
/// <param name="e"></param>
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)
{
Expand Down
1 change: 0 additions & 1 deletion API/Services/Tasks/Scanner/ParseScannedFiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,6 @@ public async Task ScanLibrariesForSeries(Library library,
}

await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.FileScanProgressEvent("File Scan Done", library.Name, ProgressEventType.Ended));
return;

async Task ProcessFolder(IList<string> files, string folder)
{
Expand Down
2 changes: 1 addition & 1 deletion API/Services/Tasks/Scanner/ProcessSeries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ public void UpdateSeriesMetadata(Series series, Library library)
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;
Expand Down
1 change: 1 addition & 0 deletions API/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJo
=>
{
opts.EnrichDiagnosticContext = LogEnricher.EnrichFromRequest;
opts.IncludeQueryInRequestPath = true;
});

app.Use(async (context, next) =>
Expand Down
4 changes: 3 additions & 1 deletion UI/Web/src/app/_models/series-detail/relation-kind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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));
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class BookLineOverlayComponent implements OnInit {
@Input({required: true}) pageNumber: number = 0;
@Input({required: true}) parent: ElementRef | undefined;
@Output() refreshToC: EventEmitter<void> = new EventEmitter();
@Output() isOpen: EventEmitter<boolean> = new EventEmitter(false);

xPath: string = '';
selectedText: string = '';
Expand Down Expand Up @@ -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;
}
Expand All @@ -96,6 +99,7 @@ export class BookLineOverlayComponent implements OnInit {
this.xPath = '//' + this.xPath;
}

this.isOpen.emit(true);
event.preventDefault();
event.stopPropagation();
}
Expand Down Expand Up @@ -137,6 +141,7 @@ export class BookLineOverlayComponent implements OnInit {
if (selection) {
selection.removeAllRanges();
}
this.isOpen.emit(false);
this.cdRef.markForCheck();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
<ng-container *transloco="let t; read: 'book-reader'">
<div class="fixed-top" #stickyTop>
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">{{t('skip-header')}}</a>
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: true}"></ng-container>
<app-book-line-overlay [parent]="bookContainerElemRef" *ngIf="page !== undefined"
[libraryId]="libraryId"
[volumeId]="volumeId"
[chapterId]="chapterId"
[seriesId]="seriesId"
[pageNumber]="pageNum"
(isOpen)="updateLineOverlayOpen($event)"
(refreshToC)="refreshPersonalToC()">
</app-book-line-overlay>
<app-drawer #commentDrawer="drawer" [(isOpen)]="drawerOpen" [options]="{topOffset: topOffset}">
Expand All @@ -18,11 +19,6 @@ <h5 class="mb-0">{{t('title')}}</h5>
<span style="font-size: 14px; color: var(--primary-color)" tabindex="0" role="button" (click)="closeReader()">{{t('close-reader')}}</span>
</div>
<div subheader>
<!-- <div class="g-0 text-center" *ngIf="!isLoading">-->
<!-- <span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">-->
<!-- (<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-mode-label')}}</span>)</span>-->
<!-- <span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>-->
<!-- </div>-->
<div class="pagination-cont">
<ng-container *ngIf="layoutMode !== BookPageLayoutMode.Default">
<div class="virt-pagination-cont">
Expand Down Expand Up @@ -129,13 +125,14 @@ <h5 class="mb-0">{{t('title')}}</h5>
<div *ngIf="page !== undefined && (scrollbarNeeded || layoutMode !== BookPageLayoutMode.Default) && !(writingStyle === WritingStyle.Vertical && layoutMode === BookPageLayoutMode.Default)"
(click)="$event.stopPropagation();"
[ngClass]="{'bottom-bar': layoutMode !== BookPageLayoutMode.Default}">
<ng-container [ngTemplateOutlet]="actionBar"></ng-container>
<ng-container [ngTemplateOutlet]="actionBar" [ngTemplateOutletContext]="{isTop: false}"></ng-container>
</div>
</div>
</div>
<ng-template #actionBar>
<ng-template #actionBar let-isTop>
<div class="action-bar row g-0 justify-content-between" *ngIf="!immersiveMode || drawerOpen || actionBarVisible">
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1" (click)="movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.BACKWARDS : PAGING_DIRECTION.FORWARD)"
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsPrevDisabled : IsNextDisabled"
title="{{readingDirection === ReadingDirection.LeftToRight ? t('previous') : t('next')}} Page">
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsPrevChapter : IsNextChapter) ? 'fa-angle-double-left' : 'fa-angle-left'}} {{readingDirection === ReadingDirection.RightToLeft ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
Expand All @@ -146,20 +143,20 @@ <h5 class="mb-0">{{t('title')}}</h5>
<button class="btn btn-secondary col-2 col-xs-1" (click)="toggleDrawer()">
<i class="fa fa-bars" aria-hidden="true"></i></button>
<div class="book-title col-2 d-none d-sm-block">
<ng-container *ngIf="isLoading; else showTitle">
@if(isLoading) {
<div class="spinner-border spinner-border-sm text-primary" style="border-radius: 50%;" role="status">
<span class="visually-hidden">{{t('loading-book')}}</span>
</div>
</ng-container>
<ng-template #showTitle>
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
} @else {
<span *ngIf="incognitoMode" (click)="turnOffIncognito()" role="button" [attr.aria-label]="t('incognito-mode-alt')">
(<i class="fa fa-glasses" aria-hidden="true"></i><span class="visually-hidden">{{t('incognito-mode-label')}}</span>)</span>
<span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>
</ng-template>
<span class="book-title-text ms-1" [ngbTooltip]="bookTitle">{{bookTitle}}</span>
}
</div>
<button class="btn btn-secondary col-2 col-xs-1" (click)="closeReader()"><i class="fa fa-times-circle" aria-hidden="true"></i></button>
<button class="btn btn-outline-secondary btn-icon col-2 col-xs-1"
[disabled]="readingDirection === ReadingDirection.LeftToRight ? IsNextDisabled : IsPrevDisabled"
(click)="!isTop && movePage(readingDirection === ReadingDirection.LeftToRight ? PAGING_DIRECTION.FORWARD : PAGING_DIRECTION.BACKWARDS)"
title="{{readingDirection === ReadingDirection.LeftToRight ? t('next') : t('previous')}} Page">
<i class="fa {{(readingDirection === ReadingDirection.LeftToRight ? IsNextChapter : IsPrevChapter) ? 'fa-angle-double-right' : 'fa-angle-right'}} {{readingDirection === ReadingDirection.LeftToRight ? 'next-page-highlight' : ''}}" aria-hidden="true"></i>
</button>
Expand Down
Loading

0 comments on commit e489d24

Please sign in to comment.