UX Overhaul Part 1 (#3047)
Co-authored-by: Joseph Milazzo <joseph.v.milazzo@gmail.com>
This commit is contained in:
parent
5934d516f3
commit
ff79710ac6
324 changed files with 11589 additions and 4598 deletions
64
API/Controllers/ColorScapeController.cs
Normal file
64
API/Controllers/ColorScapeController.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs.Theme;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
[Authorize]
|
||||
public class ColorScapeController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public ColorScapeController(IUnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the color scape for a series
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("series")]
|
||||
public async Task<ActionResult<ColorScapeDto>> GetColorScapeForSeries(int id)
|
||||
{
|
||||
var entity = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(id, User.GetUserId());
|
||||
return GetColorSpaceDto(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the color scape for a volume
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("volume")]
|
||||
public async Task<ActionResult<ColorScapeDto>> GetColorScapeForVolume(int id)
|
||||
{
|
||||
var entity = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(id, User.GetUserId());
|
||||
return GetColorSpaceDto(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the color scape for a chapter
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("chapter")]
|
||||
public async Task<ActionResult<ColorScapeDto>> GetColorScapeForChapter(int id)
|
||||
{
|
||||
var entity = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(id);
|
||||
return GetColorSpaceDto(entity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ActionResult<ColorScapeDto> GetColorSpaceDto(IHasCoverImage entity)
|
||||
{
|
||||
if (entity == null) return Ok(ColorScapeDto.Empty);
|
||||
return Ok(new ColorScapeDto(entity.PrimaryColor, entity.SecondaryColor));
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ using API.DTOs.Device;
|
|||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.SignalR;
|
||||
using AutoMapper;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
|
@ -24,20 +25,27 @@ public class DeviceController : BaseApiController
|
|||
private readonly IEmailService _emailService;
|
||||
private readonly IEventHub _eventHub;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public DeviceController(IUnitOfWork unitOfWork, IDeviceService deviceService,
|
||||
IEmailService emailService, IEventHub eventHub, ILocalizationService localizationService)
|
||||
IEmailService emailService, IEventHub eventHub, ILocalizationService localizationService, IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_deviceService = deviceService;
|
||||
_emailService = emailService;
|
||||
_eventHub = eventHub;
|
||||
_localizationService = localizationService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Device
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("create")]
|
||||
public async Task<ActionResult> CreateOrUpdateDevice(CreateDeviceDto dto)
|
||||
public async Task<ActionResult<DeviceDto>> CreateOrUpdateDevice(CreateDeviceDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
|
||||
if (user == null) return Unauthorized();
|
||||
|
|
@ -46,20 +54,22 @@ public class DeviceController : BaseApiController
|
|||
var device = await _deviceService.Create(dto, user);
|
||||
if (device == null)
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-device-create"));
|
||||
|
||||
return Ok(_mapper.Map<DeviceDto>(device));
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing Device
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("update")]
|
||||
public async Task<ActionResult> UpdateDevice(UpdateDeviceDto dto)
|
||||
public async Task<ActionResult<DeviceDto>> UpdateDevice(UpdateDeviceDto dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
|
||||
if (user == null) return Unauthorized();
|
||||
|
|
@ -67,7 +77,7 @@ public class DeviceController : BaseApiController
|
|||
|
||||
if (device == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-device-update"));
|
||||
|
||||
return Ok();
|
||||
return Ok(_mapper.Map<DeviceDto>(device));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -25,15 +25,18 @@ public class ImageController : BaseApiController
|
|||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IImageService _imageService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IReadingListService _readingListService;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageController(IUnitOfWork unitOfWork, IDirectoryService directoryService,
|
||||
IImageService imageService, ILocalizationService localizationService)
|
||||
IImageService imageService, ILocalizationService localizationService,
|
||||
IReadingListService readingListService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_directoryService = directoryService;
|
||||
_imageService = imageService;
|
||||
_localizationService = localizationService;
|
||||
_readingListService = readingListService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -42,7 +45,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("chapter-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["chapterId", "apiKey"])]
|
||||
public async Task<ActionResult> GetChapterCoverImage(int chapterId, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
|
|
@ -60,7 +63,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("library-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"libraryId", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["libraryId", "apiKey"])]
|
||||
public async Task<ActionResult> GetLibraryCoverImage(int libraryId, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
|
|
@ -78,7 +81,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("volume-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"volumeId", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["volumeId", "apiKey"])]
|
||||
public async Task<ActionResult> GetVolumeCoverImage(int volumeId, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
|
|
@ -95,7 +98,7 @@ public class ImageController : BaseApiController
|
|||
/// </summary>
|
||||
/// <param name="seriesId">Id of Series</param>
|
||||
/// <returns></returns>
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"seriesId", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["seriesId", "apiKey"])]
|
||||
[HttpGet("series-cover")]
|
||||
public async Task<ActionResult> GetSeriesCoverImage(int seriesId, string apiKey)
|
||||
{
|
||||
|
|
@ -116,7 +119,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="collectionTagId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("collection-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"collectionTagId", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["collectionTagId", "apiKey"])]
|
||||
public async Task<ActionResult> GetCollectionCoverImage(int collectionTagId, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
|
|
@ -141,15 +144,17 @@ public class ImageController : BaseApiController
|
|||
/// <param name="readingListId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("readinglist-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"readingListId", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["readingListId", "apiKey"])]
|
||||
public async Task<ActionResult> GetReadingListCoverImage(int readingListId, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
if (userId == 0) return BadRequest();
|
||||
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
|
||||
|
||||
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path))
|
||||
{
|
||||
var destFile = await GenerateReadingListCoverImage(readingListId);
|
||||
var destFile = await _readingListService.GenerateReadingListCoverImage(readingListId);
|
||||
if (string.IsNullOrEmpty(destFile)) return BadRequest(await _localizationService.Translate(userId, "no-cover-image"));
|
||||
return PhysicalFile(destFile, MimeTypeMap.GetMimeType(_directoryService.FileSystem.Path.GetExtension(destFile)), _directoryService.FileSystem.Path.GetFileName(destFile));
|
||||
}
|
||||
|
|
@ -158,22 +163,6 @@ public class ImageController : BaseApiController
|
|||
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
|
||||
}
|
||||
|
||||
private async Task<string> GenerateReadingListCoverImage(int readingListId)
|
||||
{
|
||||
var covers = await _unitOfWork.ReadingListRepository.GetRandomCoverImagesAsync(readingListId);
|
||||
var destFile = _directoryService.FileSystem.Path.Join(_directoryService.TempDirectory,
|
||||
ImageService.GetReadingListFormat(readingListId));
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
destFile += settings.EncodeMediaAs.GetExtension();
|
||||
|
||||
if (_directoryService.FileSystem.File.Exists(destFile)) return destFile;
|
||||
ImageService.CreateMergedImage(
|
||||
covers.Select(c => _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, c)).ToList(),
|
||||
settings.CoverImageSize,
|
||||
destFile);
|
||||
return !_directoryService.FileSystem.File.Exists(destFile) ? string.Empty : destFile;
|
||||
}
|
||||
|
||||
private async Task<string> GenerateCollectionCoverImage(int collectionId)
|
||||
{
|
||||
var covers = await _unitOfWork.CollectionTagRepository.GetRandomCoverImagesAsync(collectionId);
|
||||
|
|
@ -186,6 +175,7 @@ public class ImageController : BaseApiController
|
|||
covers.Select(c => _directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, c)).ToList(),
|
||||
settings.CoverImageSize,
|
||||
destFile);
|
||||
// TODO: Refactor this so that collections have a dedicated cover image so we can calculate primary/secondary colors
|
||||
return !_directoryService.FileSystem.File.Exists(destFile) ? string.Empty : destFile;
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +188,8 @@ public class ImageController : BaseApiController
|
|||
/// <param name="apiKey">API Key for user. Needed to authenticate request</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("bookmark")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId", "pageNum", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["chapterId", "pageNum", "apiKey"
|
||||
])]
|
||||
public async Task<ActionResult> GetBookmarkImage(int chapterId, int pageNum, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
|
|
@ -220,7 +211,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="apiKey"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("web-link")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Month, VaryByQueryKeys = new []{"url", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Month, VaryByQueryKeys = ["url", "apiKey"])]
|
||||
public async Task<ActionResult> GetWebLinkImage(string url, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
|
|
@ -258,7 +249,7 @@ public class ImageController : BaseApiController
|
|||
/// <returns></returns>
|
||||
[Authorize(Policy="RequireAdminRole")]
|
||||
[HttpGet("cover-upload")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"filename", "apiKey"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = ["filename", "apiKey"])]
|
||||
public async Task<ActionResult> GetCoverUploadImage(string filename, string apiKey)
|
||||
{
|
||||
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
|
||||
|
|
|
|||
|
|
@ -471,6 +471,7 @@ public class OpdsController : BaseApiController
|
|||
var feed = CreateFeed(await _localizationService.Translate(userId, "collections"), $"{apiKey}/collections", apiKey, prefix);
|
||||
SetFeedId(feed, "collections");
|
||||
|
||||
|
||||
feed.Entries.AddRange(tags.Select(tag => new FeedEntry()
|
||||
{
|
||||
Id = tag.Id.ToString(),
|
||||
|
|
@ -539,6 +540,8 @@ public class OpdsController : BaseApiController
|
|||
|
||||
var feed = CreateFeed("All Reading Lists", $"{apiKey}/reading-list", apiKey, prefix);
|
||||
SetFeedId(feed, "reading-list");
|
||||
AddPagination(feed, readingLists, $"{prefix}{apiKey}/reading-list/");
|
||||
|
||||
foreach (var readingListDto in readingLists)
|
||||
{
|
||||
feed.Entries.Add(new FeedEntry()
|
||||
|
|
@ -555,6 +558,7 @@ public class OpdsController : BaseApiController
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
return CreateXmlResult(SerializeXml(feed));
|
||||
}
|
||||
|
||||
|
|
@ -1014,7 +1018,7 @@ public class OpdsController : BaseApiController
|
|||
};
|
||||
}
|
||||
|
||||
private static void AddPagination(Feed feed, PagedList<SeriesDto> list, string href)
|
||||
private static void AddPagination<T>(Feed feed, PagedList<T> list, string href)
|
||||
{
|
||||
var url = href;
|
||||
if (href.Contains('?'))
|
||||
|
|
|
|||
|
|
@ -748,7 +748,7 @@ public class ReaderController : BaseApiController
|
|||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
|
||||
if (user == null) return new UnauthorizedResult();
|
||||
if (user.Bookmarks.IsNullOrEmpty()) return Ok();
|
||||
if (user.Bookmarks == null || user.Bookmarks.Count == 0) return Ok();
|
||||
|
||||
if (!await _accountService.HasBookmarkPermission(user))
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmark-permission"));
|
||||
|
|
|
|||
|
|
@ -210,9 +210,13 @@ public class ServerController : BaseApiController
|
|||
/// Pull the Changelog for Kavita from Github and display
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("changelog")]
|
||||
public async Task<ActionResult<IEnumerable<UpdateNotificationDto>>> GetChangelog()
|
||||
{
|
||||
// Strange bug where [Authorize] doesn't work
|
||||
if (User.GetUserId() == 0) return Unauthorized();
|
||||
|
||||
return Ok(await _versionUpdaterService.GetAllReleases());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
|
|
@ -109,6 +108,7 @@ public class UploadController : BaseApiController
|
|||
{
|
||||
series.CoverImage = filePath;
|
||||
series.CoverImageLocked = true;
|
||||
_imageService.UpdateColorScape(series);
|
||||
_unitOfWork.SeriesRepository.Update(series);
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +157,7 @@ public class UploadController : BaseApiController
|
|||
{
|
||||
tag.CoverImage = filePath;
|
||||
tag.CoverImageLocked = true;
|
||||
_imageService.UpdateColorScape(tag);
|
||||
_unitOfWork.CollectionTagRepository.Update(tag);
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +209,7 @@ public class UploadController : BaseApiController
|
|||
{
|
||||
readingList.CoverImage = filePath;
|
||||
readingList.CoverImageLocked = true;
|
||||
_imageService.UpdateColorScape(readingList);
|
||||
_unitOfWork.ReadingListRepository.Update(readingList);
|
||||
}
|
||||
|
||||
|
|
@ -327,15 +329,18 @@ public class UploadController : BaseApiController
|
|||
{
|
||||
chapter.CoverImage = filePath;
|
||||
chapter.CoverImageLocked = true;
|
||||
_imageService.UpdateColorScape(chapter);
|
||||
_unitOfWork.ChapterRepository.Update(chapter);
|
||||
|
||||
volume.CoverImage = chapter.CoverImage;
|
||||
_imageService.UpdateColorScape(volume);
|
||||
_unitOfWork.VolumeRepository.Update(volume);
|
||||
}
|
||||
|
||||
if (_unitOfWork.HasChanges())
|
||||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||
MessageFactory.CoverUpdateEvent(chapter.VolumeId, MessageFactoryEntityTypes.Volume), false);
|
||||
await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate,
|
||||
|
|
@ -391,6 +396,7 @@ public class UploadController : BaseApiController
|
|||
if (!string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
library.CoverImage = filePath;
|
||||
_imageService.UpdateColorScape(library);
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
}
|
||||
|
||||
|
|
@ -426,12 +432,15 @@ public class UploadController : BaseApiController
|
|||
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(uploadFileDto.Id);
|
||||
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
|
||||
var originalFile = chapter.CoverImage;
|
||||
|
||||
chapter.CoverImage = string.Empty;
|
||||
chapter.CoverImageLocked = false;
|
||||
_unitOfWork.ChapterRepository.Update(chapter);
|
||||
|
||||
var volume = (await _unitOfWork.VolumeRepository.GetVolumeAsync(chapter.VolumeId))!;
|
||||
volume.CoverImage = chapter.CoverImage;
|
||||
_unitOfWork.VolumeRepository.Update(volume);
|
||||
|
||||
var series = (await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId))!;
|
||||
|
||||
if (_unitOfWork.HasChanges())
|
||||
|
|
@ -451,7 +460,4 @@ public class UploadController : BaseApiController
|
|||
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reset-chapter-lock"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue