mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-03 21:54:47 -04:00
Release Testing Day 1 (#1933)
* Enhance plugin/authenticate to allow RefreshToken to be returned as well. * When typing a series name, min, or max filter, press enter to apply metadata filter. * Cleaned up the documentation around MaxCount and TotalCount * Fixed a bug where PublicationStatus wasn't being correctly set due to some strange logic I coded. * Fixed bookmark mode not having access to critical page dimensions. Fetching bookmark info api now returns dimensions by default. * Fixed pagination scaling code for different fitting options * Fixed missing code to persist page split in manga reader * Removed unneeded prefetch of blank images in bookmark mode
This commit is contained in:
parent
f99a75c2d7
commit
e3467457ea
@ -43,6 +43,7 @@ public class PluginController : BaseApiController
|
|||||||
{
|
{
|
||||||
Username = user.UserName!,
|
Username = user.UserName!,
|
||||||
Token = await _tokenService.CreateToken(user),
|
Token = await _tokenService.CreateToken(user),
|
||||||
|
RefreshToken = await _tokenService.CreateRefreshToken(user),
|
||||||
ApiKey = user.ApiKey,
|
ApiKey = user.ApiKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ public class ReaderController : BaseApiController
|
|||||||
if (chapterId <= 0) return ArraySegment<FileDimensionDto>.Empty;
|
if (chapterId <= 0) return ArraySegment<FileDimensionDto>.Empty;
|
||||||
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
|
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
|
||||||
if (chapter == null) return BadRequest("Could not find Chapter");
|
if (chapter == null) return BadRequest("Could not find Chapter");
|
||||||
return Ok(_cacheService.GetCachedFileDimensions(chapterId));
|
return Ok(_cacheService.GetCachedFileDimensions(_cacheService.GetCachePath(chapterId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -228,7 +228,7 @@ public class ReaderController : BaseApiController
|
|||||||
|
|
||||||
if (includeDimensions)
|
if (includeDimensions)
|
||||||
{
|
{
|
||||||
info.PageDimensions = _cacheService.GetCachedFileDimensions(chapterId);
|
info.PageDimensions = _cacheService.GetCachedFileDimensions(_cacheService.GetCachePath(chapterId));
|
||||||
info.DoublePairs = _readerService.GetPairs(info.PageDimensions);
|
info.DoublePairs = _readerService.GetPairs(info.PageDimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,21 +260,31 @@ public class ReaderController : BaseApiController
|
|||||||
/// Returns various information about all bookmark files for a Series. Side effect: This will cache the bookmark images for reading.
|
/// Returns various information about all bookmark files for a Series. Side effect: This will cache the bookmark images for reading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seriesId">Series Id for all bookmarks</param>
|
/// <param name="seriesId">Series Id for all bookmarks</param>
|
||||||
|
/// <param name="includeDimensions">Include file dimensions (extra I/O). Defaults to true.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("bookmark-info")]
|
[HttpGet("bookmark-info")]
|
||||||
public async Task<ActionResult<BookmarkInfoDto>> GetBookmarkInfo(int seriesId)
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"seriesId", "includeDimensions"})]
|
||||||
|
public async Task<ActionResult<BookmarkInfoDto>> GetBookmarkInfo(int seriesId, bool includeDimensions = true)
|
||||||
{
|
{
|
||||||
var totalPages = await _cacheService.CacheBookmarkForSeries(User.GetUserId(), seriesId);
|
var totalPages = await _cacheService.CacheBookmarkForSeries(User.GetUserId(), seriesId);
|
||||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.None);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.None);
|
||||||
|
|
||||||
return Ok(new BookmarkInfoDto()
|
var info = new BookmarkInfoDto()
|
||||||
{
|
{
|
||||||
SeriesName = series!.Name,
|
SeriesName = series!.Name,
|
||||||
SeriesFormat = series.Format,
|
SeriesFormat = series.Format,
|
||||||
SeriesId = series.Id,
|
SeriesId = series.Id,
|
||||||
LibraryId = series.LibraryId,
|
LibraryId = series.LibraryId,
|
||||||
Pages = totalPages,
|
Pages = totalPages,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (includeDimensions)
|
||||||
|
{
|
||||||
|
info.PageDimensions = _cacheService.GetCachedFileDimensions(_cacheService.GetBookmarkCachePath(seriesId));
|
||||||
|
info.DoublePairs = _readerService.GetPairs(info.PageDimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -606,7 +616,7 @@ public class ReaderController : BaseApiController
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
if (user?.Bookmarks == null) return Ok("Nothing to remove");
|
if (user.Bookmarks == null) return Ok("Nothing to remove");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -643,7 +653,7 @@ public class ReaderController : BaseApiController
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
if (user?.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
|
if (user.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
|
||||||
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForVolume(user.Id, volumeId));
|
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForVolume(user.Id, volumeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,7 +667,7 @@ public class ReaderController : BaseApiController
|
|||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
||||||
if (user == null) return Unauthorized();
|
if (user == null) return Unauthorized();
|
||||||
if (user?.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
|
if (user.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
|
||||||
|
|
||||||
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForSeries(user.Id, seriesId));
|
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForSeries(user.Id, seriesId));
|
||||||
}
|
}
|
||||||
@ -721,7 +731,7 @@ public class ReaderController : BaseApiController
|
|||||||
/// <param name="volumeId"></param>
|
/// <param name="volumeId"></param>
|
||||||
/// <param name="currentChapterId"></param>
|
/// <param name="currentChapterId"></param>
|
||||||
/// <returns>chapter id for next manga</returns>
|
/// <returns>chapter id for next manga</returns>
|
||||||
[ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = new string[] { "seriesId", "volumeId", "currentChapterId"})]
|
[ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = new [] { "seriesId", "volumeId", "currentChapterId"})]
|
||||||
[HttpGet("next-chapter")]
|
[HttpGet("next-chapter")]
|
||||||
public async Task<ActionResult<int>> GetNextChapter(int seriesId, int volumeId, int currentChapterId)
|
public async Task<ActionResult<int>> GetNextChapter(int seriesId, int volumeId, int currentChapterId)
|
||||||
{
|
{
|
||||||
@ -740,7 +750,7 @@ public class ReaderController : BaseApiController
|
|||||||
/// <param name="volumeId"></param>
|
/// <param name="volumeId"></param>
|
||||||
/// <param name="currentChapterId"></param>
|
/// <param name="currentChapterId"></param>
|
||||||
/// <returns>chapter id for next manga</returns>
|
/// <returns>chapter id for next manga</returns>
|
||||||
[ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = new string[] { "seriesId", "volumeId", "currentChapterId"})]
|
[ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = new [] { "seriesId", "volumeId", "currentChapterId"})]
|
||||||
[HttpGet("prev-chapter")]
|
[HttpGet("prev-chapter")]
|
||||||
public async Task<ActionResult<int>> GetPreviousChapter(int seriesId, int volumeId, int currentChapterId)
|
public async Task<ActionResult<int>> GetPreviousChapter(int seriesId, int volumeId, int currentChapterId)
|
||||||
{
|
{
|
||||||
@ -755,7 +765,7 @@ public class ReaderController : BaseApiController
|
|||||||
/// <param name="seriesId"></param>
|
/// <param name="seriesId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("time-left")]
|
[HttpGet("time-left")]
|
||||||
[ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = new string[] { "seriesId"})]
|
[ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = new [] { "seriesId"})]
|
||||||
public async Task<ActionResult<HourEstimateRangeDto>> GetEstimateToCompletion(int seriesId)
|
public async Task<ActionResult<HourEstimateRangeDto>> GetEstimateToCompletion(int seriesId)
|
||||||
{
|
{
|
||||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using API.Entities.Enums;
|
using System.Collections.Generic;
|
||||||
|
using API.Entities.Enums;
|
||||||
|
|
||||||
namespace API.DTOs.Reader;
|
namespace API.DTOs.Reader;
|
||||||
|
|
||||||
@ -10,4 +11,14 @@ public class BookmarkInfoDto
|
|||||||
public int LibraryId { get; set; }
|
public int LibraryId { get; set; }
|
||||||
public LibraryType LibraryType { get; set; }
|
public LibraryType LibraryType { get; set; }
|
||||||
public int Pages { get; set; }
|
public int Pages { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// List of all files with their inner archive structure maintained in filename and dimensions
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is optionally returned by includeDimensions</remarks>
|
||||||
|
public IEnumerable<FileDimensionDto>? PageDimensions { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// For Double Page reader, this will contain snap points to ensure the reader always resumes on correct page
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This is optionally returned by includeDimensions</remarks>
|
||||||
|
public IDictionary<int, int>? DoublePairs { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ public class ComicInfo
|
|||||||
return Math.Max(Count, (int) Math.Floor(float.Parse(Volume)));
|
return Math.Max(Count, (int) Math.Floor(float.Parse(Volume)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Count;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ public class SeriesMetadata : IHasConcurrencyToken
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalCount { get; set; } = 0;
|
public int TotalCount { get; set; } = 0;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Max number of issues/volumes in the series (Max of Volume/Issue field in ComicInfo)
|
/// Max number of issues/volumes in the series (Max of Volume/Number field in ComicInfo)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaxCount { get; set; } = 0;
|
public int MaxCount { get; set; } = 0;
|
||||||
public PublicationStatus PublicationStatus { get; set; }
|
public PublicationStatus PublicationStatus { get; set; }
|
||||||
|
@ -32,8 +32,10 @@ public interface ICacheService
|
|||||||
void CleanupChapters(IEnumerable<int> chapterIds);
|
void CleanupChapters(IEnumerable<int> chapterIds);
|
||||||
void CleanupBookmarks(IEnumerable<int> seriesIds);
|
void CleanupBookmarks(IEnumerable<int> seriesIds);
|
||||||
string GetCachedPagePath(int chapterId, int page);
|
string GetCachedPagePath(int chapterId, int page);
|
||||||
|
string GetCachePath(int chapterId);
|
||||||
|
string GetBookmarkCachePath(int seriesId);
|
||||||
IEnumerable<string> GetCachedPages(int chapterId);
|
IEnumerable<string> GetCachedPages(int chapterId);
|
||||||
IEnumerable<FileDimensionDto> GetCachedFileDimensions(int chapterId);
|
IEnumerable<FileDimensionDto> GetCachedFileDimensions(string cachePath);
|
||||||
string GetCachedBookmarkPagePath(int seriesId, int page);
|
string GetCachedBookmarkPagePath(int seriesId, int page);
|
||||||
string GetCachedFile(Chapter chapter);
|
string GetCachedFile(Chapter chapter);
|
||||||
public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile> files, bool extractPdfImages = false);
|
public void ExtractChapterFiles(string extractPath, IReadOnlyList<MangaFile> files, bool extractPdfImages = false);
|
||||||
@ -66,11 +68,15 @@ public class CacheService : ICacheService
|
|||||||
.OrderByNatural(Path.GetFileNameWithoutExtension);
|
.OrderByNatural(Path.GetFileNameWithoutExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<FileDimensionDto> GetCachedFileDimensions(int chapterId)
|
/// <summary>
|
||||||
|
/// For a given path, scan all files (in reading order) and generate File Dimensions for it. Path must exist
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cachePath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public IEnumerable<FileDimensionDto> GetCachedFileDimensions(string cachePath)
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
var path = GetCachePath(chapterId);
|
var files = _directoryService.GetFilesWithExtension(cachePath, Tasks.Scanner.Parser.Parser.ImageFileExtensions)
|
||||||
var files = _directoryService.GetFilesWithExtension(path, Tasks.Scanner.Parser.Parser.ImageFileExtensions)
|
|
||||||
.OrderByNatural(Path.GetFileNameWithoutExtension)
|
.OrderByNatural(Path.GetFileNameWithoutExtension)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
@ -94,13 +100,13 @@ public class CacheService : ICacheService
|
|||||||
Height = image.Height,
|
Height = image.Height,
|
||||||
Width = image.Width,
|
Width = image.Width,
|
||||||
IsWide = image.Width > image.Height,
|
IsWide = image.Width > image.Height,
|
||||||
FileName = file.Replace(path, string.Empty)
|
FileName = file.Replace(cachePath, string.Empty)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "There was an error calculating image dimensions for {ChapterId}", chapterId);
|
_logger.LogError(ex, "There was an error calculating image dimensions for {CachePath}", cachePath);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -259,12 +265,17 @@ public class CacheService : ICacheService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="chapterId"></param>
|
/// <param name="chapterId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private string GetCachePath(int chapterId)
|
public string GetCachePath(int chapterId)
|
||||||
{
|
{
|
||||||
return _directoryService.FileSystem.Path.GetFullPath(_directoryService.FileSystem.Path.Join(_directoryService.CacheDirectory, $"{chapterId}/"));
|
return _directoryService.FileSystem.Path.GetFullPath(_directoryService.FileSystem.Path.Join(_directoryService.CacheDirectory, $"{chapterId}/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetBookmarkCachePath(int seriesId)
|
/// <summary>
|
||||||
|
/// Returns the cache path for a given series' bookmarks. Should be cacheDirectory/{seriesId_bookmarks}/
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seriesId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetBookmarkCachePath(int seriesId)
|
||||||
{
|
{
|
||||||
return _directoryService.FileSystem.Path.GetFullPath(_directoryService.FileSystem.Path.Join(_directoryService.CacheDirectory, $"{seriesId}_bookmarks/"));
|
return _directoryService.FileSystem.Path.GetFullPath(_directoryService.FileSystem.Path.Join(_directoryService.CacheDirectory, $"{seriesId}_bookmarks/"));
|
||||||
}
|
}
|
||||||
|
@ -276,7 +276,9 @@ public class ProcessSeries : IProcessSeries
|
|||||||
// Set the AgeRating as highest in all the comicInfos
|
// Set the AgeRating as highest in all the comicInfos
|
||||||
if (!series.Metadata.AgeRatingLocked) series.Metadata.AgeRating = chapters.Max(chapter => chapter.AgeRating);
|
if (!series.Metadata.AgeRatingLocked) series.Metadata.AgeRating = chapters.Max(chapter => chapter.AgeRating);
|
||||||
|
|
||||||
|
// Count (aka expected total number of chapters or volumes from metadata) across all chapters
|
||||||
series.Metadata.TotalCount = chapters.Max(chapter => chapter.TotalCount);
|
series.Metadata.TotalCount = chapters.Max(chapter => chapter.TotalCount);
|
||||||
|
// The actual number of count's defined across all chapter's metadata
|
||||||
series.Metadata.MaxCount = chapters.Max(chapter => chapter.Count);
|
series.Metadata.MaxCount = chapters.Max(chapter => chapter.Count);
|
||||||
// To not have to rely completely on ComicInfo, try to parse out if the series is complete by checking parsed filenames as well.
|
// To not have to rely completely on ComicInfo, try to parse out if the series is complete by checking parsed filenames as well.
|
||||||
if (series.Metadata.MaxCount != series.Metadata.TotalCount)
|
if (series.Metadata.MaxCount != series.Metadata.TotalCount)
|
||||||
@ -294,7 +296,7 @@ public class ProcessSeries : IProcessSeries
|
|||||||
if (series.Metadata.MaxCount >= series.Metadata.TotalCount && series.Metadata.TotalCount > 0)
|
if (series.Metadata.MaxCount >= series.Metadata.TotalCount && series.Metadata.TotalCount > 0)
|
||||||
{
|
{
|
||||||
series.Metadata.PublicationStatus = PublicationStatus.Completed;
|
series.Metadata.PublicationStatus = PublicationStatus.Completed;
|
||||||
} else if (series.Metadata.TotalCount > 0 && series.Metadata.MaxCount > 0)
|
} else if (series.Metadata.TotalCount > 0)
|
||||||
{
|
{
|
||||||
series.Metadata.PublicationStatus = PublicationStatus.Ended;
|
series.Metadata.PublicationStatus = PublicationStatus.Ended;
|
||||||
}
|
}
|
||||||
|
@ -12,4 +12,5 @@
|
|||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Omake/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Omake/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Opds/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Opds/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=rewinded/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=rewinded/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tachiyomi/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tachiyomi/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=unbookmark/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
@ -1,3 +1,4 @@
|
|||||||
|
import { FileDimension } from "src/app/manga-reader/_models/file-dimension";
|
||||||
import { LibraryType } from "../library";
|
import { LibraryType } from "../library";
|
||||||
import { MangaFormat } from "../manga-format";
|
import { MangaFormat } from "../manga-format";
|
||||||
|
|
||||||
@ -8,4 +9,12 @@ export interface BookmarkInfo {
|
|||||||
libraryId: number;
|
libraryId: number;
|
||||||
libraryType: LibraryType;
|
libraryType: LibraryType;
|
||||||
pages: number;
|
pages: number;
|
||||||
|
/**
|
||||||
|
* This will not always be present. Depends on if asked from backend.
|
||||||
|
*/
|
||||||
|
pageDimensions?: Array<FileDimension>;
|
||||||
|
/**
|
||||||
|
* This will not always be present. Depends on if asked from backend.
|
||||||
|
*/
|
||||||
|
doublePairs?: {[key: number]: number};
|
||||||
}
|
}
|
@ -380,11 +380,11 @@
|
|||||||
<div class="row g-0 mb-2" *ngIf="metadata">
|
<div class="row g-0 mb-2" *ngIf="metadata">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
Max Items: {{metadata.maxCount}}
|
Max Items: {{metadata.maxCount}}
|
||||||
<i class="fa fa-info-circle ms-1" placement="right" ngbTooltip="Max of Volume/Issue field in ComicInfo. Used in conjunction with total items to determine publication status." role="button" tabindex="0"></i>
|
<i class="fa fa-info-circle ms-1" placement="right" ngbTooltip="Highest Count found across all ComicInfo in the Series" role="button" tabindex="0"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
Total Items: {{metadata.totalCount}}
|
Total Items: {{metadata.totalCount}}
|
||||||
<i class="fa fa-info-circle ms-1" placement="right" ngbTooltip="Total number of issues/volumes in the series" role="button" tabindex="0"></i>
|
<i class="fa fa-info-circle ms-1" placement="right" ngbTooltip="Max Issue or Volume field from all ComicInfo in the series" role="button" tabindex="0"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">Publication Status: {{metadata.publicationStatus | publicationStatus}}</div>
|
<div class="col-md-6">Publication Status: {{metadata.publicationStatus | publicationStatus}}</div>
|
||||||
<div class="col-md-6">Total Pages: {{series.pages}}</div>
|
<div class="col-md-6">Total Pages: {{series.pages}}</div>
|
||||||
|
@ -373,15 +373,23 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (this.FittingOption !== FITTING_OPTION.HEIGHT) {
|
if (this.FittingOption !== FITTING_OPTION.HEIGHT) {
|
||||||
return this.mangaReaderService.getPageDimensions(this.pageNum)?.height + 'px';
|
return this.mangaReaderService.getPageDimensions(this.pageNum)?.height + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.readingArea?.nativeElement?.clientHeight + 'px';
|
return this.readingArea?.nativeElement?.clientHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is for the pagination area
|
// This is for the pagination area
|
||||||
get MaxHeight() {
|
get MaxHeight() {
|
||||||
if (this.FittingOption !== FITTING_OPTION.HEIGHT) {
|
if (this.FittingOption === FITTING_OPTION.HEIGHT) {
|
||||||
return Math.min(this.readingArea?.nativeElement?.clientHeight, this.mangaReaderService.getPageDimensions(this.pageNum)?.height!) + 'px';
|
return 'calc(var(--vh) * 100)';
|
||||||
}
|
}
|
||||||
return 'calc(var(--vh) * 100)';
|
|
||||||
|
const needsScrolling = this.readingArea?.nativeElement?.scrollHeight > this.readingArea?.nativeElement?.clientHeight;
|
||||||
|
if (this.readingArea?.nativeElement?.clientHeight <= this.mangaReaderService.getPageDimensions(this.pageNum)?.height!) {
|
||||||
|
if (needsScrolling) {
|
||||||
|
return Math.min(this.readingArea?.nativeElement?.scrollHeight, this.mangaReaderService.getPageDimensions(this.pageNum)?.height!) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.readingArea?.nativeElement?.clientHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
get RightPaginationOffset() {
|
get RightPaginationOffset() {
|
||||||
@ -806,6 +814,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.subtitle = 'Bookmarks';
|
this.subtitle = 'Bookmarks';
|
||||||
this.libraryType = bookmarkInfo.libraryType;
|
this.libraryType = bookmarkInfo.libraryType;
|
||||||
this.maxPages = bookmarkInfo.pages;
|
this.maxPages = bookmarkInfo.pages;
|
||||||
|
this.mangaReaderService.load(bookmarkInfo);
|
||||||
|
|
||||||
// Due to change detection rules in Angular, we need to re-create the options object to apply the change
|
// Due to change detection rules in Angular, we need to re-create the options object to apply the change
|
||||||
const newOptions: Options = Object.assign({}, this.pageOptions);
|
const newOptions: Options = Object.assign({}, this.pageOptions);
|
||||||
@ -814,10 +823,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.inSetup = false;
|
this.inSetup = false;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
|
||||||
for (let i = 0; i < PREFETCH_PAGES; i++) {
|
|
||||||
this.cachedImages.push(new Image())
|
|
||||||
}
|
|
||||||
|
|
||||||
this.goToPageEvent = new BehaviorSubject<number>(this.pageNum);
|
this.goToPageEvent = new BehaviorSubject<number>(this.pageNum);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
@ -1625,7 +1630,9 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
data.readingDirection = this.readingDirection;
|
data.readingDirection = this.readingDirection;
|
||||||
data.emulateBook = modelSettings.emulateBook;
|
data.emulateBook = modelSettings.emulateBook;
|
||||||
data.swipeToPaginate = modelSettings.swipeToPaginate;
|
data.swipeToPaginate = modelSettings.swipeToPaginate;
|
||||||
this.accountService.updatePreferences(data).subscribe((updatedPrefs) => {
|
data.pageSplitOption = parseInt(modelSettings.pageSplitOption, 10);
|
||||||
|
|
||||||
|
this.accountService.updatePreferences(data).subscribe(updatedPrefs => {
|
||||||
this.toastr.success('User preferences updated');
|
this.toastr.success('User preferences updated');
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
this.user.preferences = updatedPrefs;
|
this.user.preferences = updatedPrefs;
|
||||||
|
@ -5,6 +5,7 @@ import { ReaderService } from 'src/app/_services/reader.service';
|
|||||||
import { ChapterInfo } from '../_models/chapter-info';
|
import { ChapterInfo } from '../_models/chapter-info';
|
||||||
import { DimensionMap } from '../_models/file-dimension';
|
import { DimensionMap } from '../_models/file-dimension';
|
||||||
import { FITTING_OPTION } from '../_models/reader-enums';
|
import { FITTING_OPTION } from '../_models/reader-enums';
|
||||||
|
import { BookmarkInfo } from 'src/app/_models/manga-reader/bookmark-info';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -19,7 +20,7 @@ export class ManagaReaderService {
|
|||||||
this.renderer = rendererFactory.createRenderer(null, null);
|
this.renderer = rendererFactory.createRenderer(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
load(chapterInfo: ChapterInfo) {
|
load(chapterInfo: ChapterInfo | BookmarkInfo) {
|
||||||
chapterInfo.pageDimensions!.forEach(d => {
|
chapterInfo.pageDimensions!.forEach(d => {
|
||||||
this.pageDimensions[d.pageNumber] = {
|
this.pageDimensions[d.pageNumber] = {
|
||||||
height: d.height,
|
height: d.height,
|
||||||
|
@ -321,7 +321,7 @@
|
|||||||
<label for="series-name" class="form-label me-1">Series Name</label><i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="seriesNameFilterTooltip" role="button" tabindex="0"></i>
|
<label for="series-name" class="form-label me-1">Series Name</label><i class="fa fa-info-circle" aria-hidden="true" placement="right" [ngbTooltip]="seriesNameFilterTooltip" role="button" tabindex="0"></i>
|
||||||
<span class="visually-hidden" id="filter-series-name-help"><ng-container [ngTemplateOutlet]="seriesNameFilterTooltip"></ng-container></span>
|
<span class="visually-hidden" id="filter-series-name-help"><ng-container [ngTemplateOutlet]="seriesNameFilterTooltip"></ng-container></span>
|
||||||
<ng-template #seriesNameFilterTooltip>Series name will filter against Name, Sort Name, or Localized Name</ng-template>
|
<ng-template #seriesNameFilterTooltip>Series name will filter against Name, Sort Name, or Localized Name</ng-template>
|
||||||
<input type="text" id="series-name" formControlName="seriesNameQuery" class="form-control" aria-describedby="filter-series-name-help">
|
<input type="text" id="series-name" formControlName="seriesNameQuery" class="form-control" aria-describedby="filter-series-name-help" (keyup.enter)="apply()">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -329,14 +329,14 @@
|
|||||||
<form [formGroup]="releaseYearRange" class="d-flex justify-content-between">
|
<form [formGroup]="releaseYearRange" class="d-flex justify-content-between">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="release-year-min" class="form-label">Release</label>
|
<label for="release-year-min" class="form-label">Release</label>
|
||||||
<input type="text" id="release-year-min" formControlName="min" class="form-control" style="width: 62px" placeholder="Min">
|
<input type="text" id="release-year-min" formControlName="min" class="form-control" style="width: 62px" placeholder="Min" (keyup.enter)="apply()">
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 37px !important;">
|
<div style="margin-top: 37px !important;">
|
||||||
<i class="fa-solid fa-minus" aria-hidden="true"></i>
|
<i class="fa-solid fa-minus" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3" style="margin-top: 0.5rem">
|
<div class="mb-3" style="margin-top: 0.5rem">
|
||||||
<label for="release-year-max" class="form-label"><span class="visually-hidden">Max</span></label>
|
<label for="release-year-max" class="form-label"><span class="visually-hidden">Max</span></label>
|
||||||
<input type="text" id="release-year-max" formControlName="max" class="form-control" style="width: 62px" placeholder="Max">
|
<input type="text" id="release-year-max" formControlName="max" class="form-control" style="width: 62px" placeholder="Max" (keyup.enter)="apply()">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
30
openapi.json
30
openapi.json
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.1.37"
|
"version": "0.7.1.38"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
@ -3909,6 +3909,15 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "includeDimensions",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Include file dimensions (extra I/O). Defaults to true.",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -10191,6 +10200,23 @@
|
|||||||
"pages": {
|
"pages": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"pageDimensions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/FileDimensionDto"
|
||||||
|
},
|
||||||
|
"description": "List of all files with their inner archive structure maintained in filename and dimensions",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"doublePairs": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"description": "For Double Page reader, this will contain snap points to ensure the reader always resumes on correct page",
|
||||||
|
"nullable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
@ -13559,7 +13585,7 @@
|
|||||||
},
|
},
|
||||||
"maxCount": {
|
"maxCount": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "Max number of issues/volumes in the series (Max of Volume/Issue field in ComicInfo)",
|
"description": "Max number of issues/volumes in the series (Max of Volume/Number field in ComicInfo)",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
},
|
},
|
||||||
"publicationStatus": {
|
"publicationStatus": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user