mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Manga Reader Fixes (#1925)
* Fixed up an issue where image might be cut off in fit to height * Removed some calls to backend for translating age rating to a string * Fixed an issue with sizing on page splitting right to left. * Ensure all image access requires apikey * Removed a TODO
This commit is contained in:
parent
ff18389954
commit
5c1e9c0521
@ -34,9 +34,10 @@ public class ImageController : BaseApiController
|
|||||||
/// <param name="chapterId"></param>
|
/// <param name="chapterId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("chapter-cover")]
|
[HttpGet("chapter-cover")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId"})]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId", "apiKey"})]
|
||||||
public async Task<ActionResult> GetChapterCoverImage(int chapterId)
|
public async Task<ActionResult> GetChapterCoverImage(int chapterId, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
@ -50,9 +51,10 @@ public class ImageController : BaseApiController
|
|||||||
/// <param name="libraryId"></param>
|
/// <param name="libraryId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("library-cover")]
|
[HttpGet("library-cover")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"libraryId"})]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"libraryId", "apiKey"})]
|
||||||
public async Task<ActionResult> GetLibraryCoverImage(int libraryId)
|
public async Task<ActionResult> GetLibraryCoverImage(int libraryId, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
@ -66,9 +68,10 @@ public class ImageController : BaseApiController
|
|||||||
/// <param name="volumeId"></param>
|
/// <param name="volumeId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("volume-cover")]
|
[HttpGet("volume-cover")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"volumeId"})]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"volumeId", "apiKey"})]
|
||||||
public async Task<ActionResult> GetVolumeCoverImage(int volumeId)
|
public async Task<ActionResult> GetVolumeCoverImage(int volumeId, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
@ -81,10 +84,11 @@ public class ImageController : BaseApiController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seriesId">Id of Series</param>
|
/// <param name="seriesId">Id of Series</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"seriesId"})]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"seriesId", "apiKey"})]
|
||||||
[HttpGet("series-cover")]
|
[HttpGet("series-cover")]
|
||||||
public async Task<ActionResult> GetSeriesCoverImage(int seriesId)
|
public async Task<ActionResult> GetSeriesCoverImage(int seriesId, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
@ -100,9 +104,10 @@ public class ImageController : BaseApiController
|
|||||||
/// <param name="collectionTagId"></param>
|
/// <param name="collectionTagId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("collection-cover")]
|
[HttpGet("collection-cover")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"collectionTagId"})]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"collectionTagId", "apiKey"})]
|
||||||
public async Task<ActionResult> GetCollectionCoverImage(int collectionTagId)
|
public async Task<ActionResult> GetCollectionCoverImage(int collectionTagId, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
@ -116,9 +121,10 @@ public class ImageController : BaseApiController
|
|||||||
/// <param name="readingListId"></param>
|
/// <param name="readingListId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("readinglist-cover")]
|
[HttpGet("readinglist-cover")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"readingListId"})]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"readingListId", "apiKey"})]
|
||||||
public async Task<ActionResult> GetReadingListCoverImage(int readingListId)
|
public async Task<ActionResult> GetReadingListCoverImage(int readingListId, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
|
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
|
||||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
|
||||||
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
var format = _directoryService.FileSystem.Path.GetExtension(path);
|
||||||
@ -139,6 +145,7 @@ public class ImageController : BaseApiController
|
|||||||
public async Task<ActionResult> GetBookmarkImage(int chapterId, int pageNum, string apiKey)
|
public async Task<ActionResult> GetBookmarkImage(int chapterId, int pageNum, string apiKey)
|
||||||
{
|
{
|
||||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||||
|
if (userId == 0) return BadRequest();
|
||||||
var bookmark = await _unitOfWork.UserRepository.GetBookmarkForPage(pageNum, chapterId, userId);
|
var bookmark = await _unitOfWork.UserRepository.GetBookmarkForPage(pageNum, chapterId, userId);
|
||||||
if (bookmark == null) return BadRequest("Bookmark does not exist");
|
if (bookmark == null) return BadRequest("Bookmark does not exist");
|
||||||
|
|
||||||
@ -157,9 +164,10 @@ public class ImageController : BaseApiController
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[Authorize(Policy="RequireAdminRole")]
|
[Authorize(Policy="RequireAdminRole")]
|
||||||
[HttpGet("cover-upload")]
|
[HttpGet("cover-upload")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"filename"})]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"filename", "apiKey"})]
|
||||||
public ActionResult GetCoverUploadImage(string filename)
|
public async Task<ActionResult> GetCoverUploadImage(string filename, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
if (filename.Contains("..")) return BadRequest("Invalid Filename");
|
if (filename.Contains("..")) return BadRequest("Invalid Filename");
|
||||||
|
|
||||||
var path = Path.Join(_directoryService.TempDirectory, filename);
|
var path = Path.Join(_directoryService.TempDirectory, filename);
|
||||||
|
@ -57,9 +57,10 @@ public class ReaderController : BaseApiController
|
|||||||
/// <param name="chapterId"></param>
|
/// <param name="chapterId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("pdf")]
|
[HttpGet("pdf")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId", "apiKey"})]
|
||||||
public async Task<ActionResult> GetPdf(int chapterId)
|
public async Task<ActionResult> GetPdf(int chapterId, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var chapter = await _cacheService.Ensure(chapterId);
|
var chapter = await _cacheService.Ensure(chapterId);
|
||||||
if (chapter == null) return BadRequest("There was an issue finding pdf file for reading");
|
if (chapter == null) return BadRequest("There was an issue finding pdf file for reading");
|
||||||
|
|
||||||
@ -89,14 +90,16 @@ public class ReaderController : BaseApiController
|
|||||||
/// <remarks>This will cache the chapter images for reading</remarks>
|
/// <remarks>This will cache the chapter images for reading</remarks>
|
||||||
/// <param name="chapterId">Chapter Id</param>
|
/// <param name="chapterId">Chapter Id</param>
|
||||||
/// <param name="page">Page in question</param>
|
/// <param name="page">Page in question</param>
|
||||||
|
/// <param name="apiKey">User's API Key for authentication</param>
|
||||||
/// <param name="extractPdf">Should Kavita extract pdf into images. Defaults to false.</param>
|
/// <param name="extractPdf">Should Kavita extract pdf into images. Defaults to false.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("image")]
|
[HttpGet("image")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId","page", "extractPdf", "apiKey"})]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<ActionResult> GetImage(int chapterId, int page, bool extractPdf = false)
|
public async Task<ActionResult> GetImage(int chapterId, int page, string apiKey, bool extractPdf = false)
|
||||||
{
|
{
|
||||||
if (page < 0) page = 0;
|
if (page < 0) page = 0;
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
|
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
|
||||||
if (chapter == null) return BadRequest("There was an issue finding image file for reading");
|
if (chapter == null) return BadRequest("There was an issue finding image file for reading");
|
||||||
|
|
||||||
@ -116,10 +119,11 @@ public class ReaderController : BaseApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("thumbnail")]
|
[HttpGet("thumbnail")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId", "pageNum", "apiKey"})]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<ActionResult> GetThumbnail(int chapterId, int pageNum)
|
public async Task<ActionResult> GetThumbnail(int chapterId, int pageNum, string apiKey)
|
||||||
{
|
{
|
||||||
|
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||||
var chapter = await _cacheService.Ensure(chapterId, true);
|
var chapter = await _cacheService.Ensure(chapterId, true);
|
||||||
if (chapter == null) return BadRequest("There was an issue extracting images from chapter");
|
if (chapter == null) return BadRequest("There was an issue extracting images from chapter");
|
||||||
var images = _cacheService.GetCachedPages(chapterId);
|
var images = _cacheService.GetCachedPages(chapterId);
|
||||||
@ -138,12 +142,13 @@ public class ReaderController : BaseApiController
|
|||||||
/// <remarks>We must use api key as bookmarks could be leaked to other users via the API</remarks>
|
/// <remarks>We must use api key as bookmarks could be leaked to other users via the API</remarks>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("bookmark-image")]
|
[HttpGet("bookmark-image")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour)]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"seriesId", "page", "apiKey"})]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<ActionResult> GetBookmarkImage(int seriesId, string apiKey, int page)
|
public async Task<ActionResult> GetBookmarkImage(int seriesId, string apiKey, int page)
|
||||||
{
|
{
|
||||||
if (page < 0) page = 0;
|
if (page < 0) page = 0;
|
||||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||||
|
if (userId == 0) return BadRequest();
|
||||||
|
|
||||||
var totalPages = await _cacheService.CacheBookmarkForSeries(userId, seriesId);
|
var totalPages = await _cacheService.CacheBookmarkForSeries(userId, seriesId);
|
||||||
if (page > totalPages)
|
if (page > totalPages)
|
||||||
@ -750,6 +755,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"})]
|
||||||
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,3 +1,5 @@
|
|||||||
|
$scrollbarHeight: 34px;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
@ -12,8 +14,9 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.full-height {
|
&.full-height {
|
||||||
height: calc(100vh - 34px); // 34px is the height of the horizontal scrollbar that will appear
|
height: calc(100vh - $scrollbarHeight);
|
||||||
display: flex; // changed from inline-block to fix the centering on tablets not working
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.original {
|
&.original {
|
||||||
|
@ -13,6 +13,7 @@ export class ImageService implements OnDestroy {
|
|||||||
|
|
||||||
baseUrl = environment.apiUrl;
|
baseUrl = environment.apiUrl;
|
||||||
apiKey: string = '';
|
apiKey: string = '';
|
||||||
|
encodedKey: string = '';
|
||||||
public placeholderImage = 'assets/images/image-placeholder-min.png';
|
public placeholderImage = 'assets/images/image-placeholder-min.png';
|
||||||
public errorImage = 'assets/images/error-placeholder2-min.png';
|
public errorImage = 'assets/images/error-placeholder2-min.png';
|
||||||
public resetCoverImage = 'assets/images/image-reset-cover-min.png';
|
public resetCoverImage = 'assets/images/image-reset-cover-min.png';
|
||||||
@ -33,6 +34,7 @@ export class ImageService implements OnDestroy {
|
|||||||
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
|
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.apiKey = user.apiKey;
|
this.apiKey = user.apiKey;
|
||||||
|
this.encodedKey = encodeURIComponent(this.apiKey);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -62,35 +64,35 @@ export class ImageService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLibraryCoverImage(libraryId: number) {
|
getLibraryCoverImage(libraryId: number) {
|
||||||
return this.baseUrl + 'image/library-cover?libraryId=' + libraryId;
|
return `${this.baseUrl}image/library-cover?libraryId=${libraryId}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVolumeCoverImage(volumeId: number) {
|
getVolumeCoverImage(volumeId: number) {
|
||||||
return this.baseUrl + 'image/volume-cover?volumeId=' + volumeId;
|
return `${this.baseUrl}image/volume-cover?volumeId=${volumeId}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSeriesCoverImage(seriesId: number) {
|
getSeriesCoverImage(seriesId: number) {
|
||||||
return this.baseUrl + 'image/series-cover?seriesId=' + seriesId;
|
return `${this.baseUrl}image/series-cover?seriesId=${seriesId}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCollectionCoverImage(collectionTagId: number) {
|
getCollectionCoverImage(collectionTagId: number) {
|
||||||
return this.baseUrl + 'image/collection-cover?collectionTagId=' + collectionTagId;
|
return `${this.baseUrl}image/collection-cover?collectionTagId=${collectionTagId}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadingListCoverImage(readingListId: number) {
|
getReadingListCoverImage(readingListId: number) {
|
||||||
return this.baseUrl + 'image/readinglist-cover?readingListId=' + readingListId;
|
return `${this.baseUrl}image/readinglist-cover?readingListId=${readingListId}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getChapterCoverImage(chapterId: number) {
|
getChapterCoverImage(chapterId: number) {
|
||||||
return this.baseUrl + 'image/chapter-cover?chapterId=' + chapterId;
|
return `${this.baseUrl}image/chapter-cover?chapterId=${chapterId}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBookmarkedImage(chapterId: number, pageNum: number) {
|
getBookmarkedImage(chapterId: number, pageNum: number) {
|
||||||
return this.baseUrl + 'image/bookmark?chapterId=' + chapterId + '&pageNum=' + pageNum + '&apiKey=' + encodeURIComponent(this.apiKey);
|
return `${this.baseUrl}image/bookmark?chapterId=${chapterId}&apiKey=${this.encodedKey}&pageNum=${pageNum}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCoverUploadImage(filename: string) {
|
getCoverUploadImage(filename: string) {
|
||||||
return this.baseUrl + 'image/cover-upload?filename=' + encodeURIComponent(filename);
|
return `${this.baseUrl}image/cover-upload?filename=${encodeURIComponent(filename)}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateErroredImage(event: any) {
|
updateErroredImage(event: any) {
|
||||||
|
@ -16,6 +16,9 @@ import { FilterUtilitiesService } from '../shared/_services/filter-utilities.ser
|
|||||||
import { FileDimension } from '../manga-reader/_models/file-dimension';
|
import { FileDimension } from '../manga-reader/_models/file-dimension';
|
||||||
import screenfull from 'screenfull';
|
import screenfull from 'screenfull';
|
||||||
import { TextResonse } from '../_types/text-response';
|
import { TextResonse } from '../_types/text-response';
|
||||||
|
import { AccountService } from './account.service';
|
||||||
|
import { Subject, takeUntil } from 'rxjs';
|
||||||
|
import { OnDestroy } from '@angular/core';
|
||||||
|
|
||||||
export const CHAPTER_ID_DOESNT_EXIST = -1;
|
export const CHAPTER_ID_DOESNT_EXIST = -1;
|
||||||
export const CHAPTER_ID_NOT_FETCHED = -2;
|
export const CHAPTER_ID_NOT_FETCHED = -2;
|
||||||
@ -23,16 +26,29 @@ export const CHAPTER_ID_NOT_FETCHED = -2;
|
|||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ReaderService {
|
export class ReaderService implements OnDestroy {
|
||||||
|
|
||||||
baseUrl = environment.apiUrl;
|
baseUrl = environment.apiUrl;
|
||||||
|
encodedKey: string = '';
|
||||||
|
private onDestroy: Subject<void> = new Subject();
|
||||||
|
|
||||||
// Override background color for reader and restore it onDestroy
|
// Override background color for reader and restore it onDestroy
|
||||||
private originalBodyColor!: string;
|
private originalBodyColor!: string;
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private router: Router,
|
constructor(private httpClient: HttpClient, private router: Router,
|
||||||
private location: Location, private utilityService: UtilityService,
|
private location: Location, private utilityService: UtilityService,
|
||||||
private filterUtilitySerivce: FilterUtilitiesService) { }
|
private filterUtilitySerivce: FilterUtilitiesService, private accountService: AccountService) {
|
||||||
|
this.accountService.currentUser$.pipe(takeUntil(this.onDestroy)).subscribe(user => {
|
||||||
|
if (user) {
|
||||||
|
this.encodedKey = encodeURIComponent(user.apiKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.onDestroy.next();
|
||||||
|
this.onDestroy.complete();
|
||||||
|
}
|
||||||
|
|
||||||
getNavigationArray(libraryId: number, seriesId: number, chapterId: number, format: MangaFormat) {
|
getNavigationArray(libraryId: number, seriesId: number, chapterId: number, format: MangaFormat) {
|
||||||
if (format === undefined) format = MangaFormat.ARCHIVE;
|
if (format === undefined) format = MangaFormat.ARCHIVE;
|
||||||
@ -47,7 +63,7 @@ export class ReaderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadPdf(chapterId: number) {
|
downloadPdf(chapterId: number) {
|
||||||
return this.baseUrl + 'reader/pdf?chapterId=' + chapterId;
|
return `${this.baseUrl}reader/pdf?chapterId=${chapterId}&apiKey=${this.encodedKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
bookmark(seriesId: number, volumeId: number, chapterId: number, page: number) {
|
bookmark(seriesId: number, volumeId: number, chapterId: number, page: number) {
|
||||||
@ -98,11 +114,11 @@ export class ReaderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getPageUrl(chapterId: number, page: number) {
|
getPageUrl(chapterId: number, page: number) {
|
||||||
return this.baseUrl + 'reader/image?chapterId=' + chapterId + '&page=' + page;
|
return `${this.baseUrl}reader/image?chapterId=${chapterId}&apiKey=${this.encodedKey}&page=${page}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getThumbnailUrl(chapterId: number, page: number) {
|
getThumbnailUrl(chapterId: number, page: number) {
|
||||||
return this.baseUrl + 'reader/thumbnail?chapterId=' + chapterId + '&page=' + page;
|
return `${this.baseUrl}reader/thumbnail?chapterId=${chapterId}&apiKey=${this.encodedKey}&page=${page}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBookmarkPageUrl(seriesId: number, apiKey: string, page: number) {
|
getBookmarkPageUrl(seriesId: number, apiKey: string, page: number) {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
<ng-container *ngIf="seriesMetadata.ageRating">
|
<ng-container *ngIf="seriesMetadata.ageRating">
|
||||||
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-3">
|
<div class="col-lg-1 col-md-4 col-sm-4 col-4 mb-3">
|
||||||
<app-icon-and-title label="Age Rating" [clickable]="true" fontClasses="fas fa-eye" (click)="handleGoTo(FilterQueryParam.AgeRating, seriesMetadata.ageRating)" title="Age Rating">
|
<app-icon-and-title label="Age Rating" [clickable]="true" fontClasses="fas fa-eye" (click)="handleGoTo(FilterQueryParam.AgeRating, seriesMetadata.ageRating)" title="Age Rating">
|
||||||
{{metadataService.getAgeRating(this.seriesMetadata.ageRating) | async}}
|
{{this.seriesMetadata.ageRating | ageRating | async}}
|
||||||
</app-icon-and-title>
|
</app-icon-and-title>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr d-none d-lg-block m-2"></div>
|
<div class="vr d-none d-lg-block m-2"></div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="image-container {{imageFitClass$ | async}}"
|
<div class="image-container {{imageFitClass$ | async}}"
|
||||||
[ngClass]="{'d-none': !renderWithCanvas }"
|
[ngClass]="{'d-none': !renderWithCanvas }"
|
||||||
[style.filter]="(darkenss$ | async) ?? '' | safeStyle">
|
[style.filter]="(darkenss$ | async) ?? '' | safeStyle">
|
||||||
<canvas #content ondragstart="return false;" onselectstart="return false;"></canvas>
|
<canvas #content ondragstart="return false;" onselectstart="return false;" class="{{imageFitClass$ | async}}"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export class CanvasRendererComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
constructor(private readonly cdRef: ChangeDetectorRef, private mangaReaderService: ManagaReaderService, private readerService: ReaderService) { }
|
constructor(private readonly cdRef: ChangeDetectorRef, private mangaReaderService: ManagaReaderService, private readerService: ReaderService) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.readerSettings$.pipe(takeUntil(this.onDestroy), tap(value => {
|
this.readerSettings$.pipe(takeUntil(this.onDestroy), tap((value: ReaderSetting) => {
|
||||||
this.fit = value.fitting;
|
this.fit = value.fitting;
|
||||||
this.pageSplit = value.pageSplit;
|
this.pageSplit = value.pageSplit;
|
||||||
this.layoutMode = value.layoutMode;
|
this.layoutMode = value.layoutMode;
|
||||||
@ -70,9 +70,9 @@ export class CanvasRendererComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
|
|
||||||
this.imageFitClass$ = this.readerSettings$.pipe(
|
this.imageFitClass$ = this.readerSettings$.pipe(
|
||||||
takeUntil(this.onDestroy),
|
takeUntil(this.onDestroy),
|
||||||
map(values => values.fitting),
|
map((values: ReaderSetting) => values.fitting),
|
||||||
map(fit => {
|
map(fit => {
|
||||||
if (fit === FITTING_OPTION.WIDTH || this.layoutMode === LayoutMode.Single) return fit;
|
if (fit === FITTING_OPTION.WIDTH) return fit; // || this.layoutMode === LayoutMode.Single (so that we can check the wide stuff)
|
||||||
if (this.canvasImage === null) return fit;
|
if (this.canvasImage === null) return fit;
|
||||||
|
|
||||||
// Would this ever execute given that we perform splitting only in this renderer?
|
// Would this ever execute given that we perform splitting only in this renderer?
|
||||||
@ -181,9 +181,10 @@ export class CanvasRendererComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
|
|
||||||
const needsSplitting = this.updateSplitPage();
|
const needsSplitting = this.updateSplitPage();
|
||||||
if (!needsSplitting) return;
|
if (!needsSplitting) return;
|
||||||
if (this.currentImageSplitPart === SPLIT_PAGE_PART.NO_SPLIT) return;
|
|
||||||
|
|
||||||
this.renderWithCanvas = true;
|
this.renderWithCanvas = true;
|
||||||
|
if (this.currentImageSplitPart === SPLIT_PAGE_PART.NO_SPLIT) return;
|
||||||
|
|
||||||
this.setCanvasSize();
|
this.setCanvasSize();
|
||||||
|
|
||||||
if (needsSplitting && this.currentImageSplitPart === SPLIT_PAGE_PART.LEFT_PART) {
|
if (needsSplitting && this.currentImageSplitPart === SPLIT_PAGE_PART.LEFT_PART) {
|
||||||
|
@ -392,7 +392,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get SplitIconClass() {
|
get SplitIconClass() {
|
||||||
// TODO: make this a pipe
|
|
||||||
if (this.mangaReaderService.isSplitLeftToRight(this.pageSplitOption)) {
|
if (this.mangaReaderService.isSplitLeftToRight(this.pageSplitOption)) {
|
||||||
return 'left-side';
|
return 'left-side';
|
||||||
} else if (this.mangaReaderService.isNoSplit(this.pageSplitOption)) {
|
} else if (this.mangaReaderService.isNoSplit(this.pageSplitOption)) {
|
||||||
|
@ -2,23 +2,38 @@ import { Pipe, PipeTransform } from '@angular/core';
|
|||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { AgeRating } from '../_models/metadata/age-rating';
|
import { AgeRating } from '../_models/metadata/age-rating';
|
||||||
import { AgeRatingDto } from '../_models/metadata/age-rating-dto';
|
import { AgeRatingDto } from '../_models/metadata/age-rating-dto';
|
||||||
import { MetadataService } from '../_services/metadata.service';
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'ageRating'
|
name: 'ageRating'
|
||||||
})
|
})
|
||||||
export class AgeRatingPipe implements PipeTransform {
|
export class AgeRatingPipe implements PipeTransform {
|
||||||
|
|
||||||
constructor(private metadataService: MetadataService) {}
|
constructor() {}
|
||||||
|
|
||||||
transform(value: AgeRating | AgeRatingDto | undefined): Observable<string> {
|
transform(value: AgeRating | AgeRatingDto | undefined): Observable<string> {
|
||||||
if (value === undefined || value === null) return of('undefined');
|
if (value === undefined || value === null) return of('Unknown');
|
||||||
|
|
||||||
if (value.hasOwnProperty('title')) {
|
if (value.hasOwnProperty('title')) {
|
||||||
return of((value as AgeRatingDto).title);
|
return of((value as AgeRatingDto).title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.metadataService.getAgeRating((value as AgeRating));
|
switch(value) {
|
||||||
|
case AgeRating.Unknown: return of('Unknown');
|
||||||
|
case AgeRating.EarlyChildhood: return of('Early Childhood');
|
||||||
|
case AgeRating.AdultsOnly: return of('Adults Only 18+');
|
||||||
|
case AgeRating.Everyone: return of('Everyone');
|
||||||
|
case AgeRating.Everyone10Plus: return of('Everyone 10+');
|
||||||
|
case AgeRating.G: return of('G');
|
||||||
|
case AgeRating.KidsToAdults: return of('Kids to Adults');
|
||||||
|
case AgeRating.Mature: return of('Mature');
|
||||||
|
case AgeRating.Mature17Plus: return of('M');
|
||||||
|
case AgeRating.RatingPending: return of('Rating Pending');
|
||||||
|
case AgeRating.Teen: return of('Teen');
|
||||||
|
case AgeRating.X18Plus: return of('X18+');
|
||||||
|
case AgeRating.NotApplicable: return of('Not Applicable');
|
||||||
|
}
|
||||||
|
|
||||||
|
return of('Unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
73
openapi.json
73
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.28"
|
"version": "0.7.1.32"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
@ -1845,6 +1845,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1869,6 +1876,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1893,6 +1907,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1917,6 +1938,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1941,6 +1969,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -1965,6 +2000,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -2030,6 +2072,13 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -3579,6 +3628,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -3614,6 +3670,14 @@
|
|||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"description": "User's API Key for authentication",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "extractPdf",
|
"name": "extractPdf",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
@ -3652,6 +3716,13 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32"
|
"format": "int32"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "apiKey",
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user