Manga Reader Refresh (#1137)
* Refactored manga reader to use a regular image element for all cases except for split page rendering * Fixed a weird issue where ordering of routes broke redireciton in one case. * Added comments to a lot of the enums and refactored READER_MODE to be ReaderMode and much more clearer on function. * Added bookmark effect on image renderer * Implemented keyboard shortcut modal * Introduced the new layout mode into the manga reader, updated preferences, and updated bookmark to work for said functionality. Need to implement renderer now * Hooked in ability to show double pages but all the css is broken. Committing for help from Robbie. * Fixed an issue where Language tag in metadata edit wasn't being updated * Fixed up some styling on mobile for edit series detail * Some css fixes * Hooked in ability to set background color on reader (not implemented in reader). Optimized some code in ArchiveService to avoid extra memory allocations. * Hooked in background color, generated the migration * Fixed a bug when paging to cover images, full height would be used instead of full-width for cover images * New option in reader to show screen hints (on by default). You can disable in user preferences which will stop showing pagination overlay hints * Lots of fixes for double rendering mode * Bumped the amount of cached pages to 8 * Fixed an issue where dropdowns weren't being locked on form manipulation
This commit is contained in:
parent
3dedbb1465
commit
2a4d0d1cd1
47 changed files with 3607 additions and 226 deletions
|
@ -5,6 +5,7 @@ using API.Data;
|
|||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Extensions;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
@ -14,10 +15,12 @@ namespace API.Controllers
|
|||
public class UsersController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public UsersController(IUnitOfWork unitOfWork)
|
||||
public UsersController(IUnitOfWork unitOfWork, IMapper mapper)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
|
@ -71,7 +74,10 @@ namespace API.Controllers
|
|||
existingPreferences.ScalingOption = preferencesDto.ScalingOption;
|
||||
existingPreferences.PageSplitOption = preferencesDto.PageSplitOption;
|
||||
existingPreferences.AutoCloseMenu = preferencesDto.AutoCloseMenu;
|
||||
existingPreferences.ShowScreenHints = preferencesDto.ShowScreenHints;
|
||||
existingPreferences.ReaderMode = preferencesDto.ReaderMode;
|
||||
existingPreferences.LayoutMode = preferencesDto.LayoutMode;
|
||||
existingPreferences.BackgroundColor = string.IsNullOrEmpty(preferencesDto.BackgroundColor) ? "#000000" : preferencesDto.BackgroundColor;
|
||||
existingPreferences.BookReaderMargin = preferencesDto.BookReaderMargin;
|
||||
existingPreferences.BookReaderLineSpacing = preferencesDto.BookReaderLineSpacing;
|
||||
existingPreferences.BookReaderFontFamily = preferencesDto.BookReaderFontFamily;
|
||||
|
@ -90,5 +96,13 @@ namespace API.Controllers
|
|||
|
||||
return BadRequest("There was an issue saving preferences.");
|
||||
}
|
||||
|
||||
[HttpGet("get-preferences")]
|
||||
public async Task<ActionResult<UserPreferencesDto>> GetPreferences()
|
||||
{
|
||||
return _mapper.Map<UserPreferencesDto>(
|
||||
await _unitOfWork.UserRepository.GetPreferencesAsync(User.GetUsername()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,18 +5,74 @@ namespace API.DTOs
|
|||
{
|
||||
public class UserPreferencesDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Manga Reader Option: What direction should the next/prev page buttons go
|
||||
/// </summary>
|
||||
public ReadingDirection ReadingDirection { get; set; }
|
||||
/// <summary>
|
||||
/// Manga Reader Option: How should the image be scaled to screen
|
||||
/// </summary>
|
||||
public ScalingOption ScalingOption { get; set; }
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Which side of a split image should we show first
|
||||
/// </summary>
|
||||
public PageSplitOption PageSplitOption { get; set; }
|
||||
/// <summary>
|
||||
/// Manga Reader Option: How the manga reader should perform paging or reading of the file
|
||||
/// <example>
|
||||
/// Webtoon uses scrolling to page, LeftRight uses paging by clicking left/right side of reader, UpDown uses paging
|
||||
/// by clicking top/bottom sides of reader.
|
||||
/// </example>
|
||||
/// </summary>
|
||||
public ReaderMode ReaderMode { get; set; }
|
||||
/// <summary>
|
||||
/// Manga Reader Option: How many pages to display in the reader at once
|
||||
/// </summary>
|
||||
public LayoutMode LayoutMode { get; set; }
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Background color of the reader
|
||||
/// </summary>
|
||||
public string BackgroundColor { get; set; } = "#000000";
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Allow the menu to close after 6 seconds without interaction
|
||||
/// </summary>
|
||||
public bool AutoCloseMenu { get; set; }
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Show screen hints to the user on some actions, ie) pagination direction change
|
||||
/// </summary>
|
||||
public bool ShowScreenHints { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Book Reader Option: Should the background color be dark
|
||||
/// </summary>
|
||||
public bool BookReaderDarkMode { get; set; } = false;
|
||||
/// <summary>
|
||||
/// Book Reader Option: Override extra Margin
|
||||
/// </summary>
|
||||
public int BookReaderMargin { get; set; }
|
||||
/// <summary>
|
||||
/// Book Reader Option: Override line-height
|
||||
/// </summary>
|
||||
public int BookReaderLineSpacing { get; set; }
|
||||
/// <summary>
|
||||
/// Book Reader Option: Override font size
|
||||
/// </summary>
|
||||
public int BookReaderFontSize { get; set; }
|
||||
/// <summary>
|
||||
/// Book Reader Option: Maps to the default Kavita font-family (inherit) or an override
|
||||
/// </summary>
|
||||
public string BookReaderFontFamily { get; set; }
|
||||
/// <summary>
|
||||
/// Book Reader Option: Allows tapping on side of screens to paginate
|
||||
/// </summary>
|
||||
public bool BookReaderTapToPaginate { get; set; }
|
||||
/// <summary>
|
||||
/// Book Reader Option: What direction should the next/prev page buttons go
|
||||
/// </summary>
|
||||
public ReadingDirection BookReaderReadingDirection { get; set; }
|
||||
/// <summary>
|
||||
/// UI Site Global Setting: The UI theme the user should use.
|
||||
/// </summary>
|
||||
/// <remarks>Should default to Dark</remarks>
|
||||
public SiteTheme Theme { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
1454
API/Data/Migrations/20220306155456_MangaReaderBackgroundAndLayoutMode.Designer.cs
generated
Normal file
1454
API/Data/Migrations/20220306155456_MangaReaderBackgroundAndLayoutMode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,38 @@
|
|||
using API.Entities.Enums;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class MangaReaderBackgroundAndLayoutMode : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BackgroundColor",
|
||||
table: "AppUserPreferences",
|
||||
type: "TEXT",
|
||||
defaultValue: "#000000",
|
||||
nullable: false);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "LayoutMode",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: LayoutMode.Single);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BackgroundColor",
|
||||
table: "AppUserPreferences");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LayoutMode",
|
||||
table: "AppUserPreferences");
|
||||
}
|
||||
}
|
||||
}
|
1457
API/Data/Migrations/20220307153053_ScreenHints.Designer.cs
generated
Normal file
1457
API/Data/Migrations/20220307153053_ScreenHints.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
26
API/Data/Migrations/20220307153053_ScreenHints.cs
Normal file
26
API/Data/Migrations/20220307153053_ScreenHints.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class ScreenHints : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "ShowScreenHints",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ShowScreenHints",
|
||||
table: "AppUserPreferences");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -165,6 +165,9 @@ namespace API.Data.Migrations
|
|||
b.Property<bool>("AutoCloseMenu")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BackgroundColor")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("BookReaderDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -186,6 +189,9 @@ namespace API.Data.Migrations
|
|||
b.Property<bool>("BookReaderTapToPaginate")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LayoutMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -198,6 +204,9 @@ namespace API.Data.Migrations
|
|||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("ShowScreenHints")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ThemeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
|
@ -25,12 +25,23 @@ namespace API.Entities
|
|||
/// </example>
|
||||
/// </summary>
|
||||
public ReaderMode ReaderMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Allow the menu to close after 6 seconds without interaction
|
||||
/// </summary>
|
||||
public bool AutoCloseMenu { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Show screen hints to the user on some actions, ie) pagination direction change
|
||||
/// </summary>
|
||||
public bool ShowScreenHints { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Manga Reader Option: How many pages to display in the reader at once
|
||||
/// </summary>
|
||||
public LayoutMode LayoutMode { get; set; } = LayoutMode.Single;
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Background color of the reader
|
||||
/// </summary>
|
||||
public string BackgroundColor { get; set; } = "#000000";
|
||||
/// <summary>
|
||||
/// Book Reader Option: Should the background color be dark
|
||||
/// </summary>
|
||||
public bool BookReaderDarkMode { get; set; } = true;
|
||||
|
|
11
API/Entities/Enums/LayoutMode.cs
Normal file
11
API/Entities/Enums/LayoutMode.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace API.Entities.Enums;
|
||||
|
||||
public enum LayoutMode
|
||||
{
|
||||
[Description("Single")]
|
||||
Single = 1,
|
||||
[Description("Double")]
|
||||
Double = 2
|
||||
}
|
|
@ -5,13 +5,10 @@ namespace API.Entities.Enums
|
|||
public enum ReaderMode
|
||||
{
|
||||
[Description("Left and Right")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
MANGA_LR = 0,
|
||||
LeftRight = 0,
|
||||
[Description("Up and Down")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
MANGA_UP = 1,
|
||||
UpDown = 1,
|
||||
[Description("Webtoon")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
WEBTOON = 2
|
||||
Webtoon = 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,13 @@ namespace API.Extensions
|
|||
/// <returns>Sorted Enumerable</returns>
|
||||
public static IEnumerable<T> OrderByNatural<T>(this IEnumerable<T> items, Func<T, string> selector, StringComparer stringComparer = null)
|
||||
{
|
||||
var maxDigits = items
|
||||
var list = items.ToList();
|
||||
var maxDigits = list
|
||||
.SelectMany(i => Regex.Matches(selector(i))
|
||||
.Select(digitChunk => (int?)digitChunk.Value.Length))
|
||||
.Max() ?? 0;
|
||||
|
||||
return items.OrderBy(i => Regex.Replace(selector(i), match => match.Value.PadLeft(maxDigits, '0')), stringComparer ?? StringComparer.CurrentCulture);
|
||||
return list.OrderBy(i => Regex.Replace(selector(i), match => match.Value.PadLeft(maxDigits, '0')), stringComparer ?? StringComparer.CurrentCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ namespace API.Services
|
|||
ArchiveLibrary CanOpen(string archivePath);
|
||||
bool ArchiveNeedsFlattening(ZipArchive archive);
|
||||
Task<Tuple<byte[], string>> CreateZipForDownload(IEnumerable<string> files, string tempFolder);
|
||||
string FindCoverImageFilename(string archivePath, IList<string> entryNames);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -127,8 +126,8 @@ namespace API.Services
|
|||
public static string FindFolderEntry(IEnumerable<string> entryFullNames)
|
||||
{
|
||||
var result = entryFullNames
|
||||
.OrderByNatural(Path.GetFileNameWithoutExtension)
|
||||
.Where(path => !(Path.EndsInDirectorySeparator(path) || Parser.Parser.HasBlacklistedFolderInPath(path) || path.StartsWith(Parser.Parser.MacOsMetadataFileStartsWith)))
|
||||
.OrderByNatural(Path.GetFileNameWithoutExtension)
|
||||
.FirstOrDefault(Parser.Parser.IsCoverImage);
|
||||
|
||||
return string.IsNullOrEmpty(result) ? null : result;
|
||||
|
@ -144,8 +143,8 @@ namespace API.Services
|
|||
// First check if there are any files that are not in a nested folder before just comparing by filename. This is needed
|
||||
// because NaturalSortComparer does not work with paths and doesn't seem 001.jpg as before chapter 1/001.jpg.
|
||||
var fullNames = entryFullNames
|
||||
.OrderByNatural(c => c.GetFullPathWithoutExtension())
|
||||
.Where(path => !(Path.EndsInDirectorySeparator(path) || Parser.Parser.HasBlacklistedFolderInPath(path) || path.StartsWith(Parser.Parser.MacOsMetadataFileStartsWith)) && Parser.Parser.IsImage(path))
|
||||
.OrderByNatural(c => c.GetFullPathWithoutExtension())
|
||||
.ToList();
|
||||
if (fullNames.Count == 0) return null;
|
||||
|
||||
|
@ -201,9 +200,8 @@ namespace API.Services
|
|||
case ArchiveLibrary.Default:
|
||||
{
|
||||
using var archive = ZipFile.OpenRead(archivePath);
|
||||
var entryNames = archive.Entries.Select(e => e.FullName).ToList();
|
||||
|
||||
var entryName = FindCoverImageFilename(archivePath, entryNames);
|
||||
var entryName = FindCoverImageFilename(archivePath, archive.Entries.Select(e => e.FullName));
|
||||
var entry = archive.Entries.Single(e => e.FullName == entryName);
|
||||
|
||||
using var stream = entry.Open();
|
||||
|
@ -242,7 +240,7 @@ namespace API.Services
|
|||
/// <param name="archivePath"></param>
|
||||
/// <param name="entryNames"></param>
|
||||
/// <returns></returns>
|
||||
public string FindCoverImageFilename(string archivePath, IList<string> entryNames)
|
||||
public static string FindCoverImageFilename(string archivePath, IEnumerable<string> entryNames)
|
||||
{
|
||||
var entryName = FindFolderEntry(entryNames) ?? FirstFileEntry(entryNames, Path.GetFileName(archivePath));
|
||||
return entryName;
|
||||
|
|
|
@ -79,6 +79,12 @@ public class SeriesService : ISeriesService
|
|||
series.Metadata.SummaryLocked = true;
|
||||
}
|
||||
|
||||
if (series.Metadata.Language != updateSeriesMetadataDto.SeriesMetadata.Language)
|
||||
{
|
||||
series.Metadata.Language = updateSeriesMetadataDto.SeriesMetadata?.Language;
|
||||
series.Metadata.LanguageLocked = true;
|
||||
}
|
||||
|
||||
|
||||
series.Metadata.CollectionTags ??= new List<CollectionTag>();
|
||||
UpdateRelatedList(updateSeriesMetadataDto.CollectionTags, series, allCollectionTags, (tag) =>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue