From 2723a6cd1061a0abd575b60b4240897fd99ce93a Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sun, 8 May 2022 19:52:15 -0500 Subject: [PATCH] Book Reader Redesign with e-ink focus (#1246) * Refactored the drawer into offcanvas component. Had to write some hacks to emulate how bootstrap's javascript implementation works as ngBootstrap doesn't have a component yet. * Cleaned up some of the code * Rewrote drawer to align it with the new design * First pass, refactored table of content into it's own component * Refactored all of the settings logic into a separate component. Everything is broken. * More settings on on reactive form * More code cleanup on settings * Misc fixes around the drawer code. Fixed a bug where range sliders were inheriting background color of normal text inputs * Fixed dark mode with book reader. We now clear the theme from the main app so book reader is self-contained. Styles for dark mode are injected into the reading-section. Styles that were previously in scss are now only for the actual menu system. * Cleaned up drawer styling on header * Removed an ngIf statement for click to paginate * Tweaked the accent style to have smaller font size and adjusted style on light mode. Cleaned up some clearTimeout code in a further effort to streamline codebase. * Refactored Dark mode into a basic theme. Currently styles are hardcoded. * Patched book theme in from themes branch * Patched in the backend for Book Theme (not tested yet) * Fixed a bug in seeding code for book themes. Started integration of themes into the reader settings * Everything except managing themes is working. Themes are a bit shakey, having second thoughts if we should have them or not. * Reverted the ability to do custom user book themes. Code is stable with system themes. * Stablize the Styles (#1128) * Fixed a bug where adding multiple series to reading list would throw an error on UI, but it was successful. * When a series has a reading list, we now show the connection on Series detail. * Removed all baseurl code from UI and not-connected component since we no longer use it. * Fixed tag badges not showing a border. Added last read time to the series detail page * Fixed up error interceptor to remove no-connection code * Changed implementation for series detail. Book libraries will never send chapters back. Volume 0 volumes will not be sent in volumes ever. Fixed up more renaming logic on books to send more accurate representations to the UI. * Cleaned up the selected tab and tab display logic * Fixed a bad where statement in reading lists for series * Fixed up tab logic again * Fixed a small margin on search backdrop * Made badge expander button smaller to align with badges * Fixed a few UIs due to .form-group and .form-row being removed * Updated Theme component page to help with style testing * Added more components to theme tester * Cleaned up some styling * Fixed opacity on search item hover * Bump versions by dotnet-bump-version. * Tweaked the accordion styles for light mode * Set dark book theme as default. Refactored resetSettings to be much cleaner * Started the refactor to allow book themes to affect global css variables * Fixed some issues with my css variable declarations * Fixed a close model state update * Lots of work, but dark mode on the book reader is basically done. We have to code the themes much like the site themes * Some black theme enhancements * Started working on column layout in book reader. * Cleaned up the CSS on Reader Settings * Hooked up reading direction * Got column and double column layout working * Implemented some basic virtual paging and hooked in book color theme and layout mode into user preferences. * Migration wrote, can edit page layout and color theme on book reader. Removed book dark mode since no longer needed. Fixed a bug on login/register forms where when input is focused, text is white and not black. * When loading book reader, apply column layout. * Lots of work around 2 column layout, working on images not splitting. Still not working, committing so i can merge develop in and validate code with new manga reader. * Fixed images being split into 2 BUT regression on each page boundary, total reading height is smaller and smaller * Fixed some rendering bugs where toggling column layouts would shrink images on screen constantly. Fixed a bug where bottom bar wouldn't render on column layout in some conditions (this might need to be reworked) * Started progress on progress work * Updated .NET to 6.0.4 * Fixed a bug where DataContextModelSnapshot was being removed on build thus new migrations were broken. * Tweaked the code around progress saving so that we don't loose track of last scroll element on page load * Trying to restore progress, but stuck * Extra merge stuff * Fixed a bug where volumes that are a range fail to generate series detail * No gutters on whole app. Book reader backend now applies the image class automatically at the backend. * Added wiki documentation into invite user flow and register admin user to help users understand email isn't required and they can host their own service. * Removed bottom padding * Refactored the document height to be set and removed on nav service, so the book reader and manga reader aren't broken. * Fixed the height of the action bar to simplify logic and keep the code cleaner. Refactored book service image scoping to be much more streamlined and efficient * Fixed the height of action bar to 62px and adjusted code to use the hardcoded px. (code commented) * Removed commented out code from fixed action bar height * Progress restoration seems to be working * Code cleanup * Ensure the bottom action bar is at the bottom of the viewport on small pages * Fixed book fonts not setting properly and added OpenDyslexic font. * Fixed up some font issues * Updated drawer so all sections are open by default * Switched some LINQ to use MinBy * When navigating between pages and column layout, adjust the shift for the user. * Removed some debug code * Blacklist .qpkg folders and don't scan Recently-Snapshot or recycle folders. * Renamed the scale width to be scoped to kavita to avoid conflicts. * Refactored ngx-sliders out to use normal range instead. Changed up the preferences to separate image and book settinngs into own accordion. * updated user preferences for new migration options (not committed yet) * Removed some debug code * Remove console.logs * Migration committed, let's release this to users. * A lot of crazy code just to ensure that when you close drawer the toggle reflectst that state. --- API.Tests/Services/SiteThemeServiceTests.cs | 14 +- API/API.csproj | 4 - API/Controllers/ThemeController.cs | 13 +- API/Controllers/UsersController.cs | 3 +- API/DTOs/Theme/SiteThemeDto.cs | 3 + ...teThemeDto.cs => UpdateDefaultThemeDto.cs} | 2 +- API/DTOs/UserPreferencesDto.cs | 5 +- API/Data/DataContext.cs | 19 +- ...0220508162841_BookReaderUpdate.Designer.cs | 1523 +++++++++++++++++ .../20220508162841_BookReaderUpdate.cs | 56 + .../Migrations/DataContextModelSnapshot.cs | 17 +- API/Data/Repositories/SiteThemeRepository.cs | 1 - API/Data/Seed.cs | 3 +- API/Entities/AppUserPreferences.cs | 17 +- API/Entities/Enums/BookPageLayoutMode.cs | 13 + API/Entities/Interfaces/ITheme.cs | 15 + API/Entities/SiteTheme.cs | 3 +- .../ApplicationServiceExtensions.cs | 2 +- API/Helpers/AutoMapperProfiles.cs | 8 +- API/Parser/Parser.cs | 4 +- API/Services/BookService.cs | 102 +- API/Services/DirectoryService.cs | 2 +- API/Services/MetadataService.cs | 8 +- API/Services/TaskScheduler.cs | 9 +- API/Services/Tasks/ScannerService.cs | 5 +- API/Services/Tasks/SiteThemeService.cs | 19 +- API/SignalR/MessageFactory.cs | 20 + Kavita.Common/Kavita.Common.csproj | 2 +- .../src/app/_models/book-page-layout-mode.ts | 5 + .../events/site-theme-progress-event.ts | 3 - .../_models/events/theme-progress-event.ts | 3 + .../src/app/_models/preferences/book-theme.ts | 26 + .../app/_models/preferences/preferences.ts | 7 +- .../src/app/_services/message-hub.service.ts | 4 +- UI/Web/src/app/_services/scroll.service.ts | 9 +- UI/Web/src/app/_services/theme.service.ts | 44 +- UI/Web/src/app/_services/toggle.service.ts | 39 + UI/Web/src/app/app.module.ts | 2 +- .../book-reader/_models/book-black-theme.ts | 114 ++ .../book-reader/_models/book-dark-theme.ts | 119 ++ .../book-reader/_models/book-white-theme.ts | 7 + .../src/app/book-reader/_models/theme-font.ts | 15 + .../src/app/book-reader/book-reader.module.ts | 9 +- .../book-reader/book-reader.component.html | 144 +- .../book-reader/book-reader.component.scss | 197 +-- .../book-reader/book-reader.component.ts | 1126 ++++++------ UI/Web/src/app/book-reader/book.service.ts | 16 +- .../reader-settings.component.html | 133 ++ .../reader-settings.component.scss | 9 + .../reader-settings.component.ts | 272 +++ .../table-of-contents.component.html | 25 + .../table-of-contents.component.scss | 11 + .../table-of-contents.component.ts | 48 + .../theme-test/theme-test.component.ts | 12 +- .../metadata-filter.component.html | 32 +- .../metadata-filter.component.ts | 20 +- .../reset-password.component.scss | 2 +- .../user-login/user-login.component.scss | 2 +- .../app/shared/drawer/drawer.component.html | 27 +- .../app/shared/drawer/drawer.component.scss | 62 +- .../src/app/shared/drawer/drawer.component.ts | 8 +- .../side-nav-companion-bar.component.html | 3 +- .../side-nav-companion-bar.component.ts | 3 +- .../theme-manager.component.html | 2 +- .../theme-manager/theme-manager.component.ts | 5 +- .../user-preferences.component.html | 251 +-- .../user-preferences.component.scss | 11 + .../user-preferences.component.ts | 63 +- .../app/user-settings/user-settings.module.ts | 2 - .../fonts/OpenDyslexic2/OpenDyslexic-Bold.otf | Bin 0 -> 42408 bytes .../OpenDyslexic2/OpenDyslexic-BoldItalic.otf | Bin 0 -> 78500 bytes .../OpenDyslexic2/OpenDyslexic-Italic.otf | Bin 0 -> 71120 bytes .../OpenDyslexic2/OpenDyslexic-Regular.otf | Bin 0 -> 41088 bytes UI/Web/src/theme/components/_accordion.scss | 6 +- UI/Web/src/theme/components/_input.scss | 10 +- UI/Web/src/theme/themes/dark.scss | 15 +- UI/Web/src/theme/themes/e-ink.scss | 22 +- UI/Web/src/theme/themes/light.scss | 60 +- UI/Web/src/theme/utilities/_global.scss | 1 + 79 files changed, 3708 insertions(+), 1190 deletions(-) rename API/DTOs/Theme/{UpdateDefaultSiteThemeDto.cs => UpdateDefaultThemeDto.cs} (64%) create mode 100644 API/Data/Migrations/20220508162841_BookReaderUpdate.Designer.cs create mode 100644 API/Data/Migrations/20220508162841_BookReaderUpdate.cs create mode 100644 API/Entities/Enums/BookPageLayoutMode.cs create mode 100644 API/Entities/Interfaces/ITheme.cs create mode 100644 UI/Web/src/app/_models/book-page-layout-mode.ts delete mode 100644 UI/Web/src/app/_models/events/site-theme-progress-event.ts create mode 100644 UI/Web/src/app/_models/events/theme-progress-event.ts create mode 100644 UI/Web/src/app/_models/preferences/book-theme.ts create mode 100644 UI/Web/src/app/_services/toggle.service.ts create mode 100644 UI/Web/src/app/book-reader/_models/book-black-theme.ts create mode 100644 UI/Web/src/app/book-reader/_models/book-dark-theme.ts create mode 100644 UI/Web/src/app/book-reader/_models/book-white-theme.ts create mode 100644 UI/Web/src/app/book-reader/_models/theme-font.ts create mode 100644 UI/Web/src/app/book-reader/reader-settings/reader-settings.component.html create mode 100644 UI/Web/src/app/book-reader/reader-settings/reader-settings.component.scss create mode 100644 UI/Web/src/app/book-reader/reader-settings/reader-settings.component.ts create mode 100644 UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.html create mode 100644 UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.scss create mode 100644 UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.ts create mode 100644 UI/Web/src/assets/fonts/OpenDyslexic2/OpenDyslexic-Bold.otf create mode 100644 UI/Web/src/assets/fonts/OpenDyslexic2/OpenDyslexic-BoldItalic.otf create mode 100644 UI/Web/src/assets/fonts/OpenDyslexic2/OpenDyslexic-Italic.otf create mode 100644 UI/Web/src/assets/fonts/OpenDyslexic2/OpenDyslexic-Regular.otf diff --git a/API.Tests/Services/SiteThemeServiceTests.cs b/API.Tests/Services/SiteThemeServiceTests.cs index 3f3f18acf..9c4bbc7cf 100644 --- a/API.Tests/Services/SiteThemeServiceTests.cs +++ b/API.Tests/Services/SiteThemeServiceTests.cs @@ -25,7 +25,7 @@ namespace API.Tests.Services; public class SiteThemeServiceTests { - private readonly ILogger _logger = Substitute.For>(); + private readonly ILogger _logger = Substitute.For>(); private readonly IEventHub _messageHub = Substitute.For(); private readonly DbConnection _connection; @@ -135,7 +135,7 @@ public class SiteThemeServiceTests var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new SiteThemeService(ds, _unitOfWork, _messageHub); + var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub); await siteThemeService.Scan(); Assert.NotNull(await _unitOfWork.SiteThemeRepository.GetThemeDtoByName("custom")); @@ -148,7 +148,7 @@ public class SiteThemeServiceTests var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new SiteThemeService(ds, _unitOfWork, _messageHub); + var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub); await siteThemeService.Scan(); Assert.NotNull(await _unitOfWork.SiteThemeRepository.GetThemeDtoByName("custom")); @@ -167,7 +167,7 @@ public class SiteThemeServiceTests var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new SiteThemeService(ds, _unitOfWork, _messageHub); + var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub); await siteThemeService.Scan(); Assert.NotNull(await _unitOfWork.SiteThemeRepository.GetThemeDtoByName("custom")); @@ -188,7 +188,7 @@ public class SiteThemeServiceTests var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("123")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new SiteThemeService(ds, _unitOfWork, _messageHub); + var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub); _context.SiteTheme.Add(new SiteTheme() { @@ -213,7 +213,7 @@ public class SiteThemeServiceTests var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("123")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new SiteThemeService(ds, _unitOfWork, _messageHub); + var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub); _context.SiteTheme.Add(new SiteTheme() { @@ -241,7 +241,7 @@ public class SiteThemeServiceTests var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("123")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new SiteThemeService(ds, _unitOfWork, _messageHub); + var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub); _context.SiteTheme.Add(new SiteTheme() { diff --git a/API/API.csproj b/API/API.csproj index b844745c2..1099893a3 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -94,16 +94,12 @@ - - - - diff --git a/API/Controllers/ThemeController.cs b/API/Controllers/ThemeController.cs index f6775d2dc..bf68e8641 100644 --- a/API/Controllers/ThemeController.cs +++ b/API/Controllers/ThemeController.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using API.Data; using API.DTOs.Theme; +using API.Extensions; using API.Services; using API.Services.Tasks; using Kavita.Common; @@ -13,13 +14,13 @@ namespace API.Controllers; public class ThemeController : BaseApiController { private readonly IUnitOfWork _unitOfWork; - private readonly ISiteThemeService _siteThemeService; + private readonly IThemeService _themeService; private readonly ITaskScheduler _taskScheduler; - public ThemeController(IUnitOfWork unitOfWork, ISiteThemeService siteThemeService, ITaskScheduler taskScheduler) + public ThemeController(IUnitOfWork unitOfWork, IThemeService themeService, ITaskScheduler taskScheduler) { _unitOfWork = unitOfWork; - _siteThemeService = siteThemeService; + _themeService = themeService; _taskScheduler = taskScheduler; } @@ -39,9 +40,9 @@ public class ThemeController : BaseApiController [Authorize("RequireAdminRole")] [HttpPost("update-default")] - public async Task UpdateDefault(UpdateDefaultSiteThemeDto dto) + public async Task UpdateDefault(UpdateDefaultThemeDto dto) { - await _siteThemeService.UpdateDefault(dto.ThemeId); + await _themeService.UpdateDefault(dto.ThemeId); return Ok(); } @@ -54,7 +55,7 @@ public class ThemeController : BaseApiController { try { - return Ok(await _siteThemeService.GetContent(themeId)); + return Ok(await _themeService.GetContent(themeId)); } catch (KavitaException ex) { diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 3fda79468..a896348dc 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -82,11 +82,12 @@ namespace API.Controllers existingPreferences.BookReaderMargin = preferencesDto.BookReaderMargin; existingPreferences.BookReaderLineSpacing = preferencesDto.BookReaderLineSpacing; existingPreferences.BookReaderFontFamily = preferencesDto.BookReaderFontFamily; - existingPreferences.BookReaderDarkMode = preferencesDto.BookReaderDarkMode; existingPreferences.BookReaderFontSize = preferencesDto.BookReaderFontSize; existingPreferences.BookReaderTapToPaginate = preferencesDto.BookReaderTapToPaginate; existingPreferences.BookReaderReadingDirection = preferencesDto.BookReaderReadingDirection; preferencesDto.Theme ??= await _unitOfWork.SiteThemeRepository.GetDefaultTheme(); + existingPreferences.BookThemeName = preferencesDto.BookReaderThemeName; + existingPreferences.PageLayoutMode = preferencesDto.BookReaderLayoutMode; existingPreferences.Theme = await _unitOfWork.SiteThemeRepository.GetThemeById(preferencesDto.Theme.Id); // TODO: Remove this code - this overrides layout mode to be single until the mode is released diff --git a/API/DTOs/Theme/SiteThemeDto.cs b/API/DTOs/Theme/SiteThemeDto.cs index e8b0460f9..7c44a1cd0 100644 --- a/API/DTOs/Theme/SiteThemeDto.cs +++ b/API/DTOs/Theme/SiteThemeDto.cs @@ -4,6 +4,9 @@ using API.Services; namespace API.DTOs.Theme; +/// +/// Represents a set of css overrides the user can upload to Kavita and will load into webui +/// public class SiteThemeDto { public int Id { get; set; } diff --git a/API/DTOs/Theme/UpdateDefaultSiteThemeDto.cs b/API/DTOs/Theme/UpdateDefaultThemeDto.cs similarity index 64% rename from API/DTOs/Theme/UpdateDefaultSiteThemeDto.cs rename to API/DTOs/Theme/UpdateDefaultThemeDto.cs index d4bdb8e09..0f2b129f3 100644 --- a/API/DTOs/Theme/UpdateDefaultSiteThemeDto.cs +++ b/API/DTOs/Theme/UpdateDefaultThemeDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Theme; -public class UpdateDefaultSiteThemeDto +public class UpdateDefaultThemeDto { public int ThemeId { get; set; } } diff --git a/API/DTOs/UserPreferencesDto.cs b/API/DTOs/UserPreferencesDto.cs index 4bfcb2d77..95833fa81 100644 --- a/API/DTOs/UserPreferencesDto.cs +++ b/API/DTOs/UserPreferencesDto.cs @@ -1,4 +1,5 @@ -using API.Entities; +using API.DTOs.Theme; +using API.Entities; using API.Entities.Enums; namespace API.DTOs @@ -74,5 +75,7 @@ namespace API.DTOs /// /// Should default to Dark public SiteTheme Theme { get; set; } + public string BookReaderThemeName { get; set; } + public BookPageLayoutMode BookReaderLayoutMode { get; set; } } } diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs index 90a6718f4..4f0a212b4 100644 --- a/API/Data/DataContext.cs +++ b/API/Data/DataContext.cs @@ -44,33 +44,40 @@ namespace API.Data public DbSet SeriesRelation { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) + protected override void OnModelCreating(ModelBuilder builder) { - base.OnModelCreating(modelBuilder); + base.OnModelCreating(builder); - modelBuilder.Entity() + builder.Entity() .HasMany(ur => ur.UserRoles) .WithOne(u => u.User) .HasForeignKey(ur => ur.UserId) .IsRequired(); - modelBuilder.Entity() + builder.Entity() .HasMany(ur => ur.UserRoles) .WithOne(u => u.Role) .HasForeignKey(ur => ur.RoleId) .IsRequired(); - modelBuilder.Entity() + builder.Entity() .HasOne(pt => pt.Series) .WithMany(p => p.Relations) .HasForeignKey(pt => pt.SeriesId) .OnDelete(DeleteBehavior.ClientCascade); - modelBuilder.Entity() + builder.Entity() .HasOne(pt => pt.TargetSeries) .WithMany(t => t.RelationOf) .HasForeignKey(pt => pt.TargetSeriesId); + + builder.Entity() + .Property(b => b.BookThemeName) + .HasDefaultValue("Dark"); + builder.Entity() + .Property(b => b.BackgroundColor) + .HasDefaultValue("#000000"); } diff --git a/API/Data/Migrations/20220508162841_BookReaderUpdate.Designer.cs b/API/Data/Migrations/20220508162841_BookReaderUpdate.Designer.cs new file mode 100644 index 000000000..b8e7c6082 --- /dev/null +++ b/API/Data/Migrations/20220508162841_BookReaderUpdate.Designer.cs @@ -0,0 +1,1523 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20220508162841_BookReaderUpdate")] + partial class BookReaderUpdate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("Page") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserBookmark"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + + b.Property("LayoutMode") + .HasColumnType("INTEGER"); + + b.Property("PageLayoutMode") + .HasColumnType("INTEGER"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("ShowScreenHints") + .HasColumnType("INTEGER"); + + b.Property("ThemeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId") + .IsUnique(); + + b.HasIndex("ThemeId"); + + b.ToTable("AppUserPreferences"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("PagesRead") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserProgresses"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("INTEGER"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("IsSpecial") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("Range") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TitleName") + .HasColumnType("TEXT"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("VolumeId"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("API.Entities.CollectionTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Promoted") + .IsUnique(); + + b.ToTable("CollectionTag"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("FolderPath"); + }); + + modelBuilder.Entity("API.Entities.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalTag") + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle", "ExternalTag") + .IsUnique(); + + b.ToTable("Genre"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxCount") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatusLocked") + .HasColumnType("INTEGER"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYear") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.HasIndex("Id", "SeriesId") + .IsUnique(); + + b.ToTable("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RelationKind") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("TargetSeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.HasIndex("TargetSeriesId"); + + b.ToTable("SeriesRelation"); + }); + + modelBuilder.Entity("API.Entities.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("ReadingList"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("ReadingListId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.HasIndex("ReadingListId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("VolumeId"); + + b.ToTable("ReadingListItem"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("LastChapterAdded") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("LocalizedName") + .HasColumnType("TEXT"); + + b.Property("LocalizedNameLocked") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NameLocked") + .HasColumnType("INTEGER"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("SortNameLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.ServerSetting", b => + { + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSetting"); + }); + + modelBuilder.Entity("API.Entities.SiteTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SiteTheme"); + }); + + modelBuilder.Entity("API.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalTag") + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle", "ExternalTag") + .IsUnique(); + + b.ToTable("Tag"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("ChapterGenre"); + }); + + modelBuilder.Entity("ChapterPerson", b => + { + b.Property("ChapterMetadatasId") + .HasColumnType("INTEGER"); + + b.Property("PeopleId") + .HasColumnType("INTEGER"); + + b.HasKey("ChapterMetadatasId", "PeopleId"); + + b.HasIndex("PeopleId"); + + b.ToTable("ChapterPerson"); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("ChapterTag"); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.Property("CollectionTagsId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionTagsId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("CollectionTagSeriesMetadata"); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("GenresId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("GenreSeriesMetadata"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("PersonSeriesMetadata", b => + { + b.Property("PeopleId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("PeopleId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("PersonSeriesMetadata"); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("SeriesMetadatasId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("SeriesMetadataTag"); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Bookmarks") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithOne("UserPreferences") + .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.SiteTheme", "Theme") + .WithMany() + .HasForeignKey("ThemeId"); + + b.Navigation("AppUser"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Progresses") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany("Progress") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Ratings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany("Ratings") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.HasOne("API.Entities.AppRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUser", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.HasOne("API.Entities.Volume", "Volume") + .WithMany("Chapters") + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Folders") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Files") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("Metadata") + .HasForeignKey("API.Entities.Metadata.SeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Relations") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.ClientCascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "TargetSeries") + .WithMany("RelationOf") + .HasForeignKey("TargetSeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + + b.Navigation("TargetSeries"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ReadingLists") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.ReadingList", "ReadingList") + .WithMany("Items") + .HasForeignKey("ReadingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Volume", "Volume") + .WithMany() + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("ReadingList"); + + b.Navigation("Series"); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Series") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Volumes") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("AppUsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", null) + .WithMany() + .HasForeignKey("LibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterPerson", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChapterMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Person", null) + .WithMany() + .HasForeignKey("PeopleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.HasOne("API.Entities.CollectionTag", null) + .WithMany() + .HasForeignKey("CollectionTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("PersonSeriesMetadata", b => + { + b.HasOne("API.Entities.Person", null) + .WithMany() + .HasForeignKey("PeopleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("Bookmarks"); + + b.Navigation("Progresses"); + + b.Navigation("Ratings"); + + b.Navigation("ReadingLists"); + + b.Navigation("UserPreferences"); + + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Navigation("Files"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Navigation("Metadata"); + + b.Navigation("Progress"); + + b.Navigation("Ratings"); + + b.Navigation("RelationOf"); + + b.Navigation("Relations"); + + b.Navigation("Volumes"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Navigation("Chapters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20220508162841_BookReaderUpdate.cs b/API/Data/Migrations/20220508162841_BookReaderUpdate.cs new file mode 100644 index 000000000..6df40e5fd --- /dev/null +++ b/API/Data/Migrations/20220508162841_BookReaderUpdate.cs @@ -0,0 +1,56 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Data.Migrations +{ + public partial class BookReaderUpdate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "BookReaderDarkMode", + table: "AppUserPreferences", + newName: "PageLayoutMode"); + + migrationBuilder.AlterColumn( + name: "BackgroundColor", + table: "AppUserPreferences", + type: "TEXT", + nullable: true, + defaultValue: "#000000", + oldClrType: typeof(string), + oldType: "TEXT", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "BookThemeName", + table: "AppUserPreferences", + type: "TEXT", + nullable: true, + defaultValue: "Dark"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "BookThemeName", + table: "AppUserPreferences"); + + migrationBuilder.RenameColumn( + name: "PageLayoutMode", + table: "AppUserPreferences", + newName: "BookReaderDarkMode"); + + migrationBuilder.AlterColumn( + name: "BackgroundColor", + table: "AppUserPreferences", + type: "TEXT", + nullable: true, + oldClrType: typeof(string), + oldType: "TEXT", + oldNullable: true, + oldDefaultValue: "#000000"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index e8f08eace..1c03ac40b 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -15,7 +15,7 @@ namespace API.Data.Migrations protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.3"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); modelBuilder.Entity("API.Entities.AppRole", b => { @@ -166,10 +166,9 @@ namespace API.Data.Migrations .HasColumnType("INTEGER"); b.Property("BackgroundColor") - .HasColumnType("TEXT"); - - b.Property("BookReaderDarkMode") - .HasColumnType("INTEGER"); + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); b.Property("BookReaderFontFamily") .HasColumnType("TEXT"); @@ -189,9 +188,17 @@ namespace API.Data.Migrations b.Property("BookReaderTapToPaginate") .HasColumnType("INTEGER"); + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + b.Property("LayoutMode") .HasColumnType("INTEGER"); + b.Property("PageLayoutMode") + .HasColumnType("INTEGER"); + b.Property("PageSplitOption") .HasColumnType("INTEGER"); diff --git a/API/Data/Repositories/SiteThemeRepository.cs b/API/Data/Repositories/SiteThemeRepository.cs index a95fcda23..98f9c8c87 100644 --- a/API/Data/Repositories/SiteThemeRepository.cs +++ b/API/Data/Repositories/SiteThemeRepository.cs @@ -19,7 +19,6 @@ public interface ISiteThemeRepository Task GetThemeDtoByName(string themeName); Task GetDefaultTheme(); Task> GetThemes(); - Task GetThemeById(int themeId); } diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index ec0088aba..9b7dacc2a 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; diff --git a/API/Entities/AppUserPreferences.cs b/API/Entities/AppUserPreferences.cs index d35b82e39..6caa18b79 100644 --- a/API/Entities/AppUserPreferences.cs +++ b/API/Entities/AppUserPreferences.cs @@ -25,6 +25,7 @@ namespace API.Entities /// /// public ReaderMode ReaderMode { get; set; } + /// /// Manga Reader Option: Allow the menu to close after 6 seconds without interaction /// @@ -42,10 +43,6 @@ namespace API.Entities /// public string BackgroundColor { get; set; } = "#000000"; /// - /// Book Reader Option: Should the background color be dark - /// - public bool BookReaderDarkMode { get; set; } = true; - /// /// Book Reader Option: Override extra Margin /// public int BookReaderMargin { get; set; } = 15; @@ -74,7 +71,17 @@ namespace API.Entities /// /// Should default to Dark public SiteTheme Theme { get; set; } - + /// + /// Book Reader Option: The color theme to decorate the book contents + /// + /// Should default to Dark + public string BookThemeName { get; set; } = "Dark"; + /// + /// Book Reader Option: The way a page from a book is rendered. Default is as book dictates, 1 column is fit to height, + /// 2 column is fit to height, 2 columns + /// + /// Defaults to Default + public BookPageLayoutMode PageLayoutMode { get; set; } = BookPageLayoutMode.Default; public AppUser AppUser { get; set; } diff --git a/API/Entities/Enums/BookPageLayoutMode.cs b/API/Entities/Enums/BookPageLayoutMode.cs new file mode 100644 index 000000000..dc61b5a1e --- /dev/null +++ b/API/Entities/Enums/BookPageLayoutMode.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace API.Entities.Enums; + +public enum BookPageLayoutMode +{ + [Description("Default")] + Default = 0, + [Description("1 Column")] + Column1 = 1, + [Description("2 Column")] + Column2 = 2 +} diff --git a/API/Entities/Interfaces/ITheme.cs b/API/Entities/Interfaces/ITheme.cs new file mode 100644 index 000000000..216136569 --- /dev/null +++ b/API/Entities/Interfaces/ITheme.cs @@ -0,0 +1,15 @@ +using API.Entities.Enums.Theme; + +namespace API.Entities.Interfaces; + +/// +/// A theme in some kind +/// +public interface ITheme +{ + public string Name { get; set; } + public string NormalizedName { get; set; } + public string FileName { get; set; } + public bool IsDefault { get; set; } + public ThemeProvider Provider { get; set; } +} diff --git a/API/Entities/SiteTheme.cs b/API/Entities/SiteTheme.cs index 87ebe95b1..a4847a7d6 100644 --- a/API/Entities/SiteTheme.cs +++ b/API/Entities/SiteTheme.cs @@ -8,7 +8,7 @@ namespace API.Entities; /// /// Represents a set of css overrides the user can upload to Kavita and will load into webui /// -public class SiteTheme : IEntityDate +public class SiteTheme : IEntityDate, ITheme { public int Id { get; set; } /// @@ -23,6 +23,7 @@ public class SiteTheme : IEntityDate /// File path to the content. Stored under . /// Must be a .css file /// + /// System provided themes use an alternative location as they are packaged with the app public string FileName { get; set; } /// /// Only one theme can have this. Will auto-set this as default for new user accounts diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index a4f51c67d..63e9dfdb9 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -40,7 +40,7 @@ namespace API.Extensions services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index 0a292934a..db4bb8b3c 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -107,7 +107,13 @@ namespace API.Helpers CreateMap() .ForMember(dest => dest.Theme, opt => - opt.MapFrom(src => src.Theme)); + opt.MapFrom(src => src.Theme)) + .ForMember(dest => dest.BookReaderThemeName, + opt => + opt.MapFrom(src => src.BookThemeName)) + .ForMember(dest => dest.BookReaderLayoutMode, + opt => + opt.MapFrom(src => src.PageLayoutMode)); CreateMap(); diff --git a/API/Parser/Parser.cs b/API/Parser/Parser.cs index 67b97f9c2..c17ecc716 100644 --- a/API/Parser/Parser.cs +++ b/API/Parser/Parser.cs @@ -43,7 +43,7 @@ namespace API.Parser MatchOptions, RegexTimeout); - private static readonly string XmlRegexExtensions = @"\.xml"; + private const string XmlRegexExtensions = @"\.xml"; private static readonly Regex ImageRegex = new Regex(ImageFileExtensions, MatchOptions, RegexTimeout); private static readonly Regex ArchiveFileRegex = new Regex(ArchiveFileExtensions, @@ -999,7 +999,7 @@ namespace API.Parser public static bool HasBlacklistedFolderInPath(string path) { - return path.Contains("__MACOSX") || path.StartsWith("@Recently-Snapshot") || path.StartsWith("@recycle") || path.StartsWith("._"); + return path.Contains("__MACOSX") || path.StartsWith("@Recently-Snapshot") || path.StartsWith("@recycle") || path.StartsWith("._") || path.Contains(".qpkg"); } diff --git a/API/Services/BookService.cs b/API/Services/BookService.cs index 42ec38331..4ccea99b4 100644 --- a/API/Services/BookService.cs +++ b/API/Services/BookService.cs @@ -183,6 +183,7 @@ namespace API.Services EscapeFontFamilyReferences(ref stylesheetHtml, apiBase, prepend); + // Check if there are any background images and rewrite those urls EscapeCssImageReferences(ref stylesheetHtml, apiBase, book); @@ -246,67 +247,62 @@ namespace API.Services private static void ScopeImages(HtmlDocument doc, EpubBookRef book, string apiBase) { var images = doc.DocumentNode.SelectNodes("//img"); - if (images != null) + if (images == null) return; + + foreach (var image in images) { - foreach (var image in images) + if (image.Name != "img") continue; + + string key = null; + if (image.Attributes["src"] != null) { - if (image.Name != "img") continue; + key = "src"; + } + else if (image.Attributes["xlink:href"] != null) + { + key = "xlink:href"; + } - // Need to do for xlink:href - if (image.Attributes["src"] != null) + if (string.IsNullOrEmpty(key)) continue; + + var imageFile = GetKeyForImage(book, image.Attributes[key].Value); + image.Attributes.Remove(key); + image.Attributes.Add(key, $"{apiBase}" + imageFile); + + // Add a custom class that the reader uses to ensure images stay within reader + image.AddClass("kavita-scale-width"); + } + + } + + /// + /// Returns the image key associated with the file. Contains some basic fallback logic. + /// + /// + /// + /// + private static string GetKeyForImage(EpubBookRef book, string imageFile) + { + if (!book.Content.Images.ContainsKey(imageFile)) + { + var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile)); + if (correctedKey != null) + { + imageFile = correctedKey; + } + else if (imageFile.StartsWith("..")) + { + // There are cases where the key is defined static like OEBPS/Images/1-4.jpg but reference is ../Images/1-4.jpg + correctedKey = + book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile.Replace("..", string.Empty))); + if (correctedKey != null) { - var imageFile = image.Attributes["src"].Value; - if (!book.Content.Images.ContainsKey(imageFile)) - { - // TODO: Refactor the Key code to a method to allow the hacks to be tested - var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile)); - if (correctedKey != null) - { - imageFile = correctedKey; - } else if (imageFile.StartsWith("..")) - { - // There are cases where the key is defined static like OEBPS/Images/1-4.jpg but reference is ../Images/1-4.jpg - correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile.Replace("..", string.Empty))); - if (correctedKey != null) - { - imageFile = correctedKey; - } - } - - - - } - - image.Attributes.Remove("src"); - image.Attributes.Add("src", $"{apiBase}" + imageFile); + imageFile = correctedKey; } } } - images = doc.DocumentNode.SelectNodes("//image"); - if (images != null) - { - foreach (var image in images) - { - if (image.Name != "image") continue; - - if (image.Attributes["xlink:href"] != null) - { - var imageFile = image.Attributes["xlink:href"].Value; - if (!book.Content.Images.ContainsKey(imageFile)) - { - var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile)); - if (correctedKey != null) - { - imageFile = correctedKey; - } - } - - image.Attributes.Remove("xlink:href"); - image.Attributes.Add("xlink:href", $"{apiBase}" + imageFile); - } - } - } + return imageFile; } private static string PrepareFinalHtml(HtmlDocument doc, HtmlNode body) diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index cfbd2b138..d5765bc57 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -69,7 +69,7 @@ namespace API.Services private readonly ILogger _logger; private static readonly Regex ExcludeDirectories = new Regex( - @"@eaDir|\.DS_Store|\.qpkg", + @"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)", RegexOptions.Compiled | RegexOptions.IgnoreCase); diff --git a/API/Services/MetadataService.cs b/API/Services/MetadataService.cs index 1eb154214..dcd356d88 100644 --- a/API/Services/MetadataService.cs +++ b/API/Services/MetadataService.cs @@ -62,7 +62,7 @@ public class MetadataService : IMetadataService /// Force updating cover image even if underlying file has not been modified or chapter already has a cover image private async Task UpdateChapterCoverImage(Chapter chapter, bool forceUpdate) { - var firstFile = chapter.Files.OrderBy(x => x.Chapter).FirstOrDefault(); + var firstFile = chapter.Files.MinBy(x => x.Chapter); if (!_cacheHelper.ShouldUpdateCoverImage(_directoryService.FileSystem.Path.Join(_directoryService.CoverImageDirectory, chapter.CoverImage), firstFile, chapter.Created, forceUpdate, chapter.CoverImageLocked)) return false; @@ -97,12 +97,13 @@ public class MetadataService : IMetadataService null, volume.Created, forceUpdate)) return false; volume.Chapters ??= new List(); - var firstChapter = volume.Chapters.OrderBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting).FirstOrDefault(); + var firstChapter = volume.Chapters.MinBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting); if (firstChapter == null) return false; volume.CoverImage = firstChapter.CoverImage; await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, MessageFactory.CoverUpdateEvent(volume.Id, MessageFactoryEntityTypes.Volume), false); + return true; } @@ -133,8 +134,7 @@ public class MetadataService : IMetadataService if (!_cacheHelper.CoverImageExists(coverImage)) { - coverImage = series.Volumes[0].Chapters.OrderBy(c => double.Parse(c.Number), _chapterSortComparerForInChapterSorting) - .FirstOrDefault()?.CoverImage; + coverImage = series.Volumes[0].Chapters.MinBy(c => double.Parse(c.Number), _chapterSortComparerForInChapterSorting)?.CoverImage; } } series.CoverImage = firstCover?.CoverImage ?? coverImage; diff --git a/API/Services/TaskScheduler.cs b/API/Services/TaskScheduler.cs index d749c20ca..585bec476 100644 --- a/API/Services/TaskScheduler.cs +++ b/API/Services/TaskScheduler.cs @@ -36,7 +36,7 @@ public class TaskScheduler : ITaskScheduler private readonly IStatsService _statsService; private readonly IVersionUpdaterService _versionUpdaterService; - private readonly ISiteThemeService _siteThemeService; + private readonly IThemeService _themeService; public static BackgroundJobServer Client => new BackgroundJobServer(); private static readonly Random Rnd = new Random(); @@ -45,7 +45,7 @@ public class TaskScheduler : ITaskScheduler public TaskScheduler(ICacheService cacheService, ILogger logger, IScannerService scannerService, IUnitOfWork unitOfWork, IMetadataService metadataService, IBackupService backupService, ICleanupService cleanupService, IStatsService statsService, IVersionUpdaterService versionUpdaterService, - ISiteThemeService siteThemeService) + IThemeService themeService) { _cacheService = cacheService; _logger = logger; @@ -56,7 +56,7 @@ public class TaskScheduler : ITaskScheduler _cleanupService = cleanupService; _statsService = statsService; _versionUpdaterService = versionUpdaterService; - _siteThemeService = siteThemeService; + _themeService = themeService; } public async Task ScheduleTasks() @@ -131,7 +131,7 @@ public class TaskScheduler : ITaskScheduler public void ScanSiteThemes() { _logger.LogInformation("Starting Site Theme scan"); - BackgroundJob.Enqueue(() => _siteThemeService.Scan()); + BackgroundJob.Enqueue(() => _themeService.Scan()); } #endregion @@ -149,6 +149,7 @@ public class TaskScheduler : ITaskScheduler public void ScanLibrary(int libraryId) { _logger.LogInformation("Enqueuing library scan for: {LibraryId}", libraryId); + // TODO: If a library scan is already queued up for libraryId, don't do anything BackgroundJob.Enqueue(() => _scannerService.ScanLibrary(libraryId)); // When we do a scan, force cache to re-unpack in case page numbers change BackgroundJob.Enqueue(() => _cleanupService.CleanupCacheDirectory()); diff --git a/API/Services/Tasks/ScannerService.cs b/API/Services/Tasks/ScannerService.cs index a5c0f0d5d..a9a96b71f 100644 --- a/API/Services/Tasks/ScannerService.cs +++ b/API/Services/Tasks/ScannerService.cs @@ -301,6 +301,9 @@ public class ScannerService : IScannerService await CleanupDbEntities(); + // await _eventHub.SendMessageAsync(SignalREvents.NotificationProgress, + // MessageFactory.ScanLibraryProgressEvent(libraryId, 1F)); + BackgroundJob.Enqueue(() => _metadataService.RefreshMetadata(libraryId, false)); } @@ -712,7 +715,7 @@ public class ScannerService : IScannerService } } - // BUG: The issue here is that people is just from chapter, but series metadata might already have some people on it + // NOTE: The issue here is that people is just from chapter, but series metadata might already have some people on it // I might be able to filter out people that are in locked fields? var people = chapters.SelectMany(c => c.People).ToList(); PersonHelper.KeepOnlySamePeopleBetweenLists(series.Metadata.People, diff --git a/API/Services/Tasks/SiteThemeService.cs b/API/Services/Tasks/SiteThemeService.cs index e0e1bc2d8..553730d3a 100644 --- a/API/Services/Tasks/SiteThemeService.cs +++ b/API/Services/Tasks/SiteThemeService.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Data; @@ -7,24 +6,23 @@ using API.Entities; using API.Entities.Enums.Theme; using API.SignalR; using Kavita.Common; -using Microsoft.AspNetCore.SignalR; namespace API.Services.Tasks; -public interface ISiteThemeService +public interface IThemeService { Task GetContent(int themeId); Task Scan(); Task UpdateDefault(int themeId); } -public class SiteThemeService : ISiteThemeService +public class ThemeService : IThemeService { private readonly IDirectoryService _directoryService; private readonly IUnitOfWork _unitOfWork; private readonly IEventHub _eventHub; - public SiteThemeService(IDirectoryService directoryService, IUnitOfWork unitOfWork, IEventHub eventHub) + public ThemeService(IDirectoryService directoryService, IUnitOfWork unitOfWork, IEventHub eventHub) { _directoryService = directoryService; _unitOfWork = unitOfWork; @@ -36,7 +34,6 @@ public class SiteThemeService : ISiteThemeService /// /// /// - /// public async Task GetContent(int themeId) { var theme = await _unitOfWork.SiteThemeRepository.GetThemeDto(themeId); @@ -55,7 +52,8 @@ public class SiteThemeService : ISiteThemeService { _directoryService.ExistOrCreate(_directoryService.SiteThemeDirectory); var reservedNames = Seed.DefaultThemes.Select(t => t.NormalizedName).ToList(); - var themeFiles = _directoryService.GetFilesWithExtension(Parser.Parser.NormalizePath(_directoryService.SiteThemeDirectory), @"\.css") + var themeFiles = _directoryService + .GetFilesWithExtension(Parser.Parser.NormalizePath(_directoryService.SiteThemeDirectory), @"\.css") .Where(name => !reservedNames.Contains(Parser.Parser.Normalize(name))).ToList(); var allThemes = (await _unitOfWork.SiteThemeRepository.GetThemes()).ToList(); @@ -91,7 +89,8 @@ public class SiteThemeService : ISiteThemeService }); await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, - MessageFactory.SiteThemeProgressEvent(_directoryService.FileSystem.Path.GetFileName(themeFile), themeName, ProgressEventType.Updated)); + MessageFactory.SiteThemeProgressEvent(_directoryService.FileSystem.Path.GetFileName(themeFile), themeName, + ProgressEventType.Updated)); } @@ -116,10 +115,10 @@ public class SiteThemeService : ISiteThemeService } await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, - MessageFactory.SiteThemeProgressEvent("", "", ProgressEventType.Ended)); - + MessageFactory.SiteThemeProgressEvent("", "", ProgressEventType.Ended)); } + /// /// Removes the theme and any references to it from Pref and sets them to the default at the time. /// This commits to DB. diff --git a/API/SignalR/MessageFactory.cs b/API/SignalR/MessageFactory.cs index 50bfa5039..be3ab0acf 100644 --- a/API/SignalR/MessageFactory.cs +++ b/API/SignalR/MessageFactory.cs @@ -66,6 +66,10 @@ namespace API.SignalR /// private const string SiteThemeProgress = "SiteThemeProgress"; /// + /// A custom book theme was removed or added + /// + private const string BookThemeProgress = "BookThemeProgress"; + /// /// A type of event that has progress (determinate or indeterminate). /// The underlying event will have a name to give details on how to handle. /// @@ -367,5 +371,21 @@ namespace API.SignalR } }; } + + public static SignalRMessage BookThemeProgressEvent(string subtitle, string themeName, string eventType) + { + return new SignalRMessage() + { + Name = BookThemeProgress, + Title = "Scanning Book Theme", + SubTitle = subtitle, + EventType = eventType, + Progress = ProgressType.Indeterminate, + Body = new + { + ThemeName = themeName, + } + }; + } } } diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 7125068ac..debc74845 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -19,4 +19,4 @@ - \ No newline at end of file + diff --git a/UI/Web/src/app/_models/book-page-layout-mode.ts b/UI/Web/src/app/_models/book-page-layout-mode.ts new file mode 100644 index 000000000..aac6c3fdb --- /dev/null +++ b/UI/Web/src/app/_models/book-page-layout-mode.ts @@ -0,0 +1,5 @@ +export enum BookPageLayoutMode { + Default = 0, + Column1 = 1, + Column2 = 2, +} \ No newline at end of file diff --git a/UI/Web/src/app/_models/events/site-theme-progress-event.ts b/UI/Web/src/app/_models/events/site-theme-progress-event.ts deleted file mode 100644 index 23fab2939..000000000 --- a/UI/Web/src/app/_models/events/site-theme-progress-event.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface SiteThemeProgressEvent { - themeName: string; -} \ No newline at end of file diff --git a/UI/Web/src/app/_models/events/theme-progress-event.ts b/UI/Web/src/app/_models/events/theme-progress-event.ts new file mode 100644 index 000000000..84120e32a --- /dev/null +++ b/UI/Web/src/app/_models/events/theme-progress-event.ts @@ -0,0 +1,3 @@ +export interface ThemeProgressEvent { + themeName: string; +} \ No newline at end of file diff --git a/UI/Web/src/app/_models/preferences/book-theme.ts b/UI/Web/src/app/_models/preferences/book-theme.ts new file mode 100644 index 000000000..4b487fb12 --- /dev/null +++ b/UI/Web/src/app/_models/preferences/book-theme.ts @@ -0,0 +1,26 @@ +import { ThemeProvider } from "./site-theme"; + +/** + * Theme for the the book reader contents + */ + export interface BookTheme { + name: string; + provider: ThemeProvider; + /** + * Main color (usually background color) that represents the theme + */ + colorHash: string; + isDefault: boolean; + /** + * Is this theme providing dark mode to the reader aka Should we style the reader controls to be dark mode + */ + isDarkTheme: boolean; + /** + * Used to identify the theme on style tag + */ + selector: string; + /** + * Inner HTML + */ + content: string; + } diff --git a/UI/Web/src/app/_models/preferences/preferences.ts b/UI/Web/src/app/_models/preferences/preferences.ts index 7fbdaf185..065eb577b 100644 --- a/UI/Web/src/app/_models/preferences/preferences.ts +++ b/UI/Web/src/app/_models/preferences/preferences.ts @@ -1,5 +1,6 @@ import { LayoutMode } from 'src/app/manga-reader/_models/layout-mode'; +import { BookPageLayoutMode } from '../book-page-layout-mode'; import { PageSplitOption } from './page-split-option'; import { ReaderMode } from './reader-mode'; import { ReadingDirection } from './reading-direction'; @@ -16,15 +17,16 @@ export interface Preferences { layoutMode: LayoutMode; backgroundColor: string; showScreenHints: boolean; - + // Book Reader - bookReaderDarkMode: boolean; bookReaderMargin: number; bookReaderLineSpacing: number; bookReaderFontSize: number; bookReaderFontFamily: string; bookReaderTapToPaginate: boolean; bookReaderReadingDirection: ReadingDirection; + bookReaderThemeName: string; + bookReaderLayoutMode: BookPageLayoutMode; // Global theme: SiteTheme; @@ -35,3 +37,4 @@ export const scalingOptions = [{text: 'Automatic', value: ScalingOption.Automati export const pageSplitOptions = [{text: 'Fit to Screen', value: PageSplitOption.FitSplit}, {text: 'Right to Left', value: PageSplitOption.SplitRightToLeft}, {text: 'Left to Right', value: PageSplitOption.SplitLeftToRight}, {text: 'No Split', value: PageSplitOption.NoSplit}]; export const readingModes = [{text: 'Left to Right', value: ReaderMode.LeftRight}, {text: 'Up to Down', value: ReaderMode.UpDown}, {text: 'Webtoon', value: ReaderMode.Webtoon}]; export const layoutModes = [{text: 'Single', value: LayoutMode.Single}, {text: 'Double', value: LayoutMode.Double}, {text: 'Double (Manga)', value: LayoutMode.DoubleReversed}]; +export const bookLayoutModes = [{text: 'Default', value: BookPageLayoutMode.Default}, {text: '1 Column', value: BookPageLayoutMode.Column1}, {text: '2 Column', value: BookPageLayoutMode.Column2}]; diff --git a/UI/Web/src/app/_services/message-hub.service.ts b/UI/Web/src/app/_services/message-hub.service.ts index ac7619611..852d8a906 100644 --- a/UI/Web/src/app/_services/message-hub.service.ts +++ b/UI/Web/src/app/_services/message-hub.service.ts @@ -6,7 +6,7 @@ import { BehaviorSubject, ReplaySubject } from 'rxjs'; import { environment } from 'src/environments/environment'; import { LibraryModifiedEvent } from '../_models/events/library-modified-event'; import { NotificationProgressEvent } from '../_models/events/notification-progress-event'; -import { SiteThemeProgressEvent } from '../_models/events/site-theme-progress-event'; +import { ThemeProgressEvent } from '../_models/events/theme-progress-event'; import { User } from '../_models/user'; export enum EVENTS { @@ -157,7 +157,7 @@ export class MessageHubService { this.hubConnection.on(EVENTS.SiteThemeProgress, resp => { this.messagesSource.next({ event: EVENTS.SiteThemeProgress, - payload: resp.body as SiteThemeProgressEvent + payload: resp.body as ThemeProgressEvent }); }); diff --git a/UI/Web/src/app/_services/scroll.service.ts b/UI/Web/src/app/_services/scroll.service.ts index 15e00b89d..7c4b07ea2 100644 --- a/UI/Web/src/app/_services/scroll.service.ts +++ b/UI/Web/src/app/_services/scroll.service.ts @@ -18,5 +18,12 @@ export class ScrollService { top: top, behavior: 'smooth' }); - } + } + + scrollToX(left: number, el: Element | Window = window) { + el.scroll({ + left: left, + behavior: 'auto' + }); + } } diff --git a/UI/Web/src/app/_services/theme.service.ts b/UI/Web/src/app/_services/theme.service.ts index 8165cc235..23dc8e90c 100644 --- a/UI/Web/src/app/_services/theme.service.ts +++ b/UI/Web/src/app/_services/theme.service.ts @@ -10,13 +10,13 @@ import { SiteTheme, ThemeProvider } from '../_models/preferences/site-theme'; import { EVENTS, MessageHubService } from './message-hub.service'; - @Injectable({ providedIn: 'root' }) export class ThemeService implements OnDestroy { public defaultTheme: string = 'dark'; + public defaultBookTheme: string = 'Dark'; private currentThemeSource = new ReplaySubject(1); public currentTheme$ = this.currentThemeSource.asObservable(); @@ -32,9 +32,9 @@ export class ThemeService implements OnDestroy { private readonly onDestroy = new Subject(); private renderer: Renderer2; private baseUrl = environment.apiUrl; - - constructor(rendererFactory: RendererFactory2, @Inject(DOCUMENT) private document: Document, private httpClient: HttpClient, + + constructor(rendererFactory: RendererFactory2, @Inject(DOCUMENT) private document: Document, private httpClient: HttpClient, messageHub: MessageHubService, private domSantizer: DomSanitizer, private confirmService: ConfirmService) { this.renderer = rendererFactory.createRenderer(null, null); @@ -47,7 +47,7 @@ export class ThemeService implements OnDestroy { if (notificationEvent.name !== EVENTS.SiteThemeProgress) return; if (notificationEvent.eventType === 'ended') { - this.getThemes().subscribe(() => {}); + if (notificationEvent.name === EVENTS.SiteThemeProgress) this.getThemes().subscribe(() => {}); } }); } @@ -61,6 +61,10 @@ export class ThemeService implements OnDestroy { return getComputedStyle(this.document.body).getPropertyValue('--color-scheme').trim(); } + getCssVariable(variable: string) { + return getComputedStyle(this.document.body).getPropertyValue(variable).trim(); + } + isDarkTheme() { return this.getColorScheme().toLowerCase() === 'dark'; } @@ -73,6 +77,13 @@ export class ThemeService implements OnDestroy { })); } + /** + * Used in book reader to remove all themes so book reader can provide custom theming options + */ + clearThemes() { + this.unsetThemes(); + } + setDefault(themeId: number) { return this.httpClient.post(this.baseUrl + 'theme/update-default', {themeId: themeId}).pipe(map(() => { // Refresh the cache when a default state is changed @@ -84,8 +95,25 @@ export class ThemeService implements OnDestroy { return this.httpClient.post(this.baseUrl + 'theme/scan', {}); } + /** + * Sets the book theme on the body tag so css variable overrides can take place + * @param selector brtheme- prefixed string + */ + setBookTheme(selector: string) { + this.unsetBookThemes(); + this.renderer.addClass(this.document.querySelector('body'), selector); + } - setTheme(themeName: string) { + clearBookTheme() { + this.unsetBookThemes(); + } + + + /** + * Sets the theme as active. Will inject a style tag into document to load a custom theme and apply the selector to the body + * @param themeName + */ + setTheme(themeName: string) { const theme = this.themeCache.find(t => t.name.toLowerCase() === themeName.toLowerCase()); if (theme) { this.unsetThemes(); @@ -132,4 +160,10 @@ export class ThemeService implements OnDestroy { private unsetThemes() { this.themeCache.forEach(theme => this.document.body.classList.remove(theme.selector)); } + + private unsetBookThemes() { + Array.from(this.document.body.classList).filter(cls => cls.startsWith('brtheme-')).forEach(c => this.document.body.classList.remove(c)); + } + + } diff --git a/UI/Web/src/app/_services/toggle.service.ts b/UI/Web/src/app/_services/toggle.service.ts new file mode 100644 index 000000000..5b9e90bd4 --- /dev/null +++ b/UI/Web/src/app/_services/toggle.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { NavigationStart, Router } from '@angular/router'; +import { filter, ReplaySubject, take } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ToggleService { + + toggleState: boolean = false; + + + private toggleStateSource: ReplaySubject = new ReplaySubject(1); + public toggleState$ = this.toggleStateSource.asObservable(); + + constructor(router: Router) { + router.events + .pipe(filter(event => event instanceof NavigationStart)) + .subscribe((event) => { + this.toggleState = false; + }); + this.toggleStateSource.next(false); + } + + toggle() { + this.toggleState = !this.toggleState; + this.toggleStateSource.pipe(take(1)).subscribe(state => { + this.toggleState = !state; + console.log('Toggle: ', this.toggleState) + this.toggleStateSource.next(this.toggleState); + }); + + } + + set(state: boolean) { + this.toggleState = state; + this.toggleStateSource.next(state); + } +} diff --git a/UI/Web/src/app/app.module.ts b/UI/Web/src/app/app.module.ts index b7c96c26f..926fb3218 100644 --- a/UI/Web/src/app/app.module.ts +++ b/UI/Web/src/app/app.module.ts @@ -26,7 +26,7 @@ import { NavModule } from './nav/nav.module'; AppRoutingModule, BrowserAnimationsModule, - SidenavModule, + SidenavModule, NavModule, ToastrModule.forRoot({ diff --git a/UI/Web/src/app/book-reader/_models/book-black-theme.ts b/UI/Web/src/app/book-reader/_models/book-black-theme.ts new file mode 100644 index 000000000..f0dcebc89 --- /dev/null +++ b/UI/Web/src/app/book-reader/_models/book-black-theme.ts @@ -0,0 +1,114 @@ +// Important note about themes. Must have one section with .reader-container that contains color, background-color and rest of the styles must be scoped to .book-content +export const BookBlackTheme = ` +:root .brtheme-black { + /* General */ + --color-scheme: dark; + --bs-body-color: black; + --hr-color: rgba(239, 239, 239, 0.125); + --accent-bg-color: rgba(1, 4, 9, 0.5); + --accent-text-color: lightgrey; + --body-text-color: #efefef; + --btn-icon-filter: invert(1) grayscale(100%) brightness(200%); + + /* Drawer */ + --drawer-bg-color: #292929; + --drawer-text-color: white; + + /* Accordion */ + --accordion-header-text-color: rgba(74, 198, 148, 0.9); + --accordion-header-bg-color: rgba(52, 60, 70, 0.5); + --accordion-body-bg-color: #292929; + --accordion-body-border-color: rgba(239, 239, 239, 0.125); + --accordion-body-text-color: var(--body-text-color); + --accordion-header-collapsed-text-color: rgba(74, 198, 148, 0.9); + --accordion-header-collapsed-bg-color: #292929; + --accordion-button-focus-border-color: unset; + --accordion-button-focus-box-shadow: unset; + --accordion-active-body-bg-color: #292929; + + /* Buttons */ + --btn-focus-boxshadow-color: rgb(255 255 255 / 50%); + --btn-primary-text-color: white; + --btn-primary-bg-color: var(--primary-color); + --btn-primary-border-color: var(--primary-color); + --btn-primary-hover-text-color: white; + --btn-primary-hover-bg-color: var(--primary-color-darker-shade); + --btn-primary-hover-border-color: var(--primary-color-darker-shade); + --btn-alt-bg-color: #424c72; + --btn-alt-border-color: #444f75; + --btn-alt-hover-bg-color: #3b4466; + --btn-alt-focus-bg-color: #343c59; + --btn-alt-focus-boxshadow-color: rgb(255 255 255 / 50%); + --btn-fa-icon-color: white; + --btn-disabled-bg-color: #343a40; + --btn-disabled-text-color: white; + --btn-disabled-border-color: #6c757d; + + /* Nav (Tabs) */ + --nav-tab-border-color: rgba(44, 118, 88, 0.7); + --nav-tab-text-color: var(--body-text-color); + --nav-tab-bg-color: var(--primary-color); + --nav-tab-hover-border-color: var(--primary-color); + --nav-tab-active-text-color: white; + --nav-tab-border-hover-color: transparent; + --nav-tab-hover-text-color: var(--body-text-color); + --nav-tab-hover-bg-color: transparent; + --nav-tab-border-top: rgba(44, 118, 88, 0.7); + --nav-tab-border-left: rgba(44, 118, 88, 0.7); + --nav-tab-border-bottom: rgba(44, 118, 88, 0.7); + --nav-tab-border-right: rgba(44, 118, 88, 0.7); + --nav-tab-hover-border-top: rgba(44, 118, 88, 0.7); + --nav-tab-hover-border-left: rgba(44, 118, 88, 0.7); + --nav-tab-hover-border-bottom: var(--bs-body-bg); + --nav-tab-hover-border-right: rgba(44, 118, 88, 0.7); + --nav-tab-active-hover-bg-color: var(--primary-color); + --nav-link-bg-color: var(--primary-color); + --nav-link-active-text-color: white; + --nav-link-text-color: white; + + + + /* Reading Bar */ + --br-actionbar-button-text-color: white; + --br-actionbar-button-hover-border-color: #6c757d; + --br-actionbar-bg-color: black; +} +} + + + +.book-content *:not(input), .book-content *:not(select), .book-content *:not(code), .book-content *:not(:link), .book-content *:not(.ngx-toastr) { + color: #dcdcdc !important; +} + +.book-content code { + color: #e83e8c !important; +} + +.book-content :link, .book-content a { + color: #8db2e5 !important; +} + +.book-content img, .book-content img[src] { +z-index: 1; +filter: brightness(0.85) !important; +background-color: initial !important; +} + +.reader-container { + color: #dcdcdc !important; + background-image: none !important; + background-color: black !important; +} + +.book-content *:not(code), .book-content *:not(a) { + background-color: black; + box-shadow: none; + text-shadow: none; + border-radius: unset; + color: #dcdcdc !important; +} + +.book-content :visited, .book-content :visited *, .book-content :visited *[class] {color: rgb(211, 138, 138) !important} +.book-content :link:not(cite), :link .book-content *:not(cite) {color: #8db2e5 !important} +`; \ No newline at end of file diff --git a/UI/Web/src/app/book-reader/_models/book-dark-theme.ts b/UI/Web/src/app/book-reader/_models/book-dark-theme.ts new file mode 100644 index 000000000..04be8b258 --- /dev/null +++ b/UI/Web/src/app/book-reader/_models/book-dark-theme.ts @@ -0,0 +1,119 @@ +// Important note about themes. Styles must be scoped to .book-content if not css variable overrides +export const BookDarkTheme = ` +:root .brtheme-dark { + /* General */ + --color-scheme: dark; + --bs-body-color: #292929; + --hr-color: rgba(239, 239, 239, 0.125); + --accent-bg-color: rgba(1, 4, 9, 0.5); + --accent-text-color: lightgrey; + --body-text-color: #efefef; + --btn-icon-filter: invert(1) grayscale(100%) brightness(200%); + + /* Drawer */ + --drawer-bg-color: #292929; + --drawer-text-color: white; + + /* Accordion */ + --accordion-header-text-color: rgba(74, 198, 148, 0.9); + --accordion-header-bg-color: rgba(52, 60, 70, 0.5); + --accordion-body-bg-color: #292929; + --accordion-body-border-color: rgba(239, 239, 239, 0.125); + --accordion-body-text-color: var(--body-text-color); + --accordion-header-collapsed-text-color: rgba(74, 198, 148, 0.9); + --accordion-header-collapsed-bg-color: #292929; + --accordion-button-focus-border-color: unset; + --accordion-button-focus-box-shadow: unset; + --accordion-active-body-bg-color: #292929; + + /* Buttons */ + --btn-focus-boxshadow-color: rgb(255 255 255 / 50%); + --btn-primary-text-color: white; + --btn-primary-bg-color: var(--primary-color); + --btn-primary-border-color: var(--primary-color); + --btn-primary-hover-text-color: white; + --btn-primary-hover-bg-color: var(--primary-color-darker-shade); + --btn-primary-hover-border-color: var(--primary-color-darker-shade); + --btn-alt-bg-color: #424c72; + --btn-alt-border-color: #444f75; + --btn-alt-hover-bg-color: #3b4466; + --btn-alt-focus-bg-color: #343c59; + --btn-alt-focus-boxshadow-color: rgb(255 255 255 / 50%); + --btn-fa-icon-color: white; + --btn-disabled-bg-color: #343a40; + --btn-disabled-text-color: white; + --btn-disabled-border-color: #6c757d; + + /* Nav (Tabs) */ + --nav-tab-border-color: rgba(44, 118, 88, 0.7); + --nav-tab-text-color: var(--body-text-color); + --nav-tab-bg-color: var(--primary-color); + --nav-tab-hover-border-color: var(--primary-color); + --nav-tab-active-text-color: white; + --nav-tab-border-hover-color: transparent; + --nav-tab-hover-text-color: var(--body-text-color); + --nav-tab-hover-bg-color: transparent; + --nav-tab-border-top: rgba(44, 118, 88, 0.7); + --nav-tab-border-left: rgba(44, 118, 88, 0.7); + --nav-tab-border-bottom: rgba(44, 118, 88, 0.7); + --nav-tab-border-right: rgba(44, 118, 88, 0.7); + --nav-tab-hover-border-top: rgba(44, 118, 88, 0.7); + --nav-tab-hover-border-left: rgba(44, 118, 88, 0.7); + --nav-tab-hover-border-bottom: var(--bs-body-bg); + --nav-tab-hover-border-right: rgba(44, 118, 88, 0.7); + --nav-tab-active-hover-bg-color: var(--primary-color); + --nav-link-bg-color: var(--primary-color); + --nav-link-active-text-color: white; + --nav-link-text-color: white; + + /* Checkboxes/Switch */ + --checkbox-checked-bg-color: var(--primary-color); + --checkbox-border-color: var(--input-focused-border-color); + --checkbox-focus-border-color: var(--primary-color); + --checkbox-focus-boxshadow-color: rgb(255 255 255 / 50%); + + + + /* Reading Bar */ + --br-actionbar-button-text-color: white; + --br-actionbar-button-hover-border-color: #6c757d; + --br-actionbar-bg-color: black; +} + + + +.book-content *:not(input), .book-content *:not(select), .book-content *:not(code), .book-content *:not(:link), .book-content *:not(.ngx-toastr) { + color: #dcdcdc !important; +} + +.book-content code { + color: #e83e8c !important; +} + +.book-content :link, .book-content a { + color: #8db2e5 !important; +} + +.book-content img, .book-content img[src] { +z-index: 1; +filter: brightness(0.85) !important; +background-color: initial !important; +} + +.reader-container { + color: #dcdcdc !important; + background-image: none !important; + background-color: #292929 !important; +} + +.book-content *:not(code), .book-content *:not(a) { + background-color: #292929; + box-shadow: none; + text-shadow: none; + border-radius: unset; + color: #dcdcdc !important; +} + +.book-content :visited, .book-content :visited *, .book-content :visited *[class] {color: rgb(211, 138, 138) !important} +.book-content :link:not(cite), :link .book-content *:not(cite) {color: #8db2e5 !important} +`; \ No newline at end of file diff --git a/UI/Web/src/app/book-reader/_models/book-white-theme.ts b/UI/Web/src/app/book-reader/_models/book-white-theme.ts new file mode 100644 index 000000000..8061e2eb3 --- /dev/null +++ b/UI/Web/src/app/book-reader/_models/book-white-theme.ts @@ -0,0 +1,7 @@ +// Important note about themes. Must have one section with .reader-container that contains color, background-color and rest of the styles must be scoped to .book-content +export const BookWhiteTheme = ` + :root() .brtheme-white { + --brtheme-link-text-color: green; + --brtheme-bg-color: lightgrey; + } +`; \ No newline at end of file diff --git a/UI/Web/src/app/book-reader/_models/theme-font.ts b/UI/Web/src/app/book-reader/_models/theme-font.ts new file mode 100644 index 000000000..06d3648dd --- /dev/null +++ b/UI/Web/src/app/book-reader/_models/theme-font.ts @@ -0,0 +1,15 @@ +/** + * A font family to inject into the book reader + */ +export interface ThemeFont { + /** + * Name/Font-family + */ + fontFamily: string; + /** + * Where the font is loaded from? + */ + fontSrc: string; + format: 'truetype'; + +} \ No newline at end of file diff --git a/UI/Web/src/app/book-reader/book-reader.module.ts b/UI/Web/src/app/book-reader/book-reader.module.ts index a572d1a96..50bd0060b 100644 --- a/UI/Web/src/app/book-reader/book-reader.module.ts +++ b/UI/Web/src/app/book-reader/book-reader.module.ts @@ -5,12 +5,14 @@ import { BookReaderRoutingModule } from './book-reader.router.module'; import { SharedModule } from '../shared/shared.module'; import { SafeStylePipe } from './safe-style.pipe'; import { ReactiveFormsModule } from '@angular/forms'; -import { NgbProgressbarModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { PipeModule } from '../pipe/pipe.module'; +import { NgbAccordionModule, NgbNavModule, NgbProgressbarModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; +import { TableOfContentsComponent } from './table-of-contents/table-of-contents.component'; +import { ReaderSettingsComponent } from './reader-settings/reader-settings.component'; @NgModule({ - declarations: [BookReaderComponent, SafeStylePipe], + declarations: [BookReaderComponent, SafeStylePipe, TableOfContentsComponent, ReaderSettingsComponent], imports: [ CommonModule, BookReaderRoutingModule, @@ -19,6 +21,9 @@ import { PipeModule } from '../pipe/pipe.module'; NgbProgressbarModule, NgbTooltipModule, PipeModule, + NgbTooltipModule, + NgbAccordionModule, // Drawer + NgbNavModule, // Drawer ], exports: [ BookReaderComponent, SafeStylePipe diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.html b/UI/Web/src/app/book-reader/book-reader/book-reader.component.html index 701aa46a5..0e84d47cc 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.html +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.html @@ -2,74 +2,11 @@
Skip to main content - -
-

Book Settings - - -

-
-
-
-
- -
-
- - -
-
-
-
- - - {{pageStyles['font-size']}} - -
-
- - - {{pageStyles['line-height']}} - -
-
- - - {{pageStyles['margin-right']}} - -
-
- - -
-
- - - -
-
- - The ability to click the sides of the page to page left and right - The ability to click the sides of the page to page left and right - -
-
- - Put reader in fullscreen mode - - - - -
-
- -
-
+ +
+ Book Settings +
+
{{pageNum}}
@@ -79,51 +16,56 @@
{{maxPages - 1}}
-
-

Table of Contents

-
- This book does not have Table of Contents set in the metadata or a toc file -
-
- -
- - - -
+
+
+ + +
-
+
-
-
-
+ +
+
+
-
+
-
+
- {{bookTitle}} (Incognito Mode) + {{bookTitle}}
diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss b/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss index 1d70b604d..c4a1784d4 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.scss @@ -28,135 +28,77 @@ src: url(../../../assets/fonts/RocknRoll_One/RocknRollOne-Regular.ttf) format("truetype"); } +@font-face { + font-family: "OpenDyslexic2"; + src: url(../../../assets/fonts/OpenDyslexic2/OpenDyslexic-Regular.otf) format("opentype"); +} + +:root { + --br-actionbar-button-text-color: #6c757d; + --accordion-body-bg-color: black; + --accordion-header-bg-color: grey; + --br-actionbar-button-hover-border-color: #6c757d; + --br-actionbar-bg-color: white; +} + + $dark-form-background-no-opacity: rgb(1, 4, 9); $primary-color: #0062cc; + +// Drawer .control-container { padding-bottom: 5px; } -.table-of-contents li { - cursor: pointer; - - &.active { - font-weight: bold; - } -} - .page-stub { margin-top: 6px; padding-left: 2px; padding-right: 2px; } +// Drawer End + .fixed-top { z-index: 1022; } -.dark-mode { - - color: #dcdcdc !important; - background-image: none !important; - background-color: #292929 !important; - - *:not(code), *:not(a) { - background-color: #292929; - box-shadow: none; - text-shadow: none; - border-radius: unset; - color: #dcdcdc !important; - } - - *:not(input), *:not(code), *:not(:link) { - color: #dcdcdc !important; - } - - code { - color: #e83e8c !important; - } - - .btn-icon { - background-color: transparent; - } - - :link, a { - color: #8db2e5 !important; - } - - img, img[src] { - z-index: 1; - filter: brightness(0.85) !important; - background-color: initial !important; - } - - :visited, :visited *, :visited *[class] {color: rgb(211, 138, 138) !important} - :link:not(cite), :link *:not(cite) {color: #8db2e5 !important} +.dark-mode .overlay { + opacity: 0; } -.reading-bar { - background-color: white; + +.action-bar { + background-color: var(--br-actionbar-bg-color); overflow: hidden; box-shadow: 0 0 6px 0 rgb(0 0 0 / 70%); -} + max-height: 62px; -.dark-mode { - .reading-bar, .book-title, .drawer-body, .drawer-container { - background-color: $dark-form-background-no-opacity; + .book-title-text { + text-align: center; + text-overflow: ellipsis; } - button { - background-color: $dark-form-background-no-opacity; - } - - .btn { - &.btn-secondary { - border-color: transparent; - &:hover, &:focus { - border-color: #545b62; - } + @media(max-width: 875px) { + .book-title { + display: none; } - - &.btn-outline-secondary { - border-color: transparent; + } - &:hover, &:focus { - border-color: #545b62; - } - } - - span { - background-color: unset; - } - - i { - background-color: unset; - } - } -} - -::ng-deep .dark-mode .drawer-container { - .header, body, *:not(.progress-bar) { - background-color: $dark-form-background-no-opacity !important; - } -} - -@media(max-width: 875px) { .book-title { - display: none; + margin-top: 10px; + text-align: center; + text-transform: capitalize; } - -} - -.book-title { - margin-top: 10px; - text-align: center; - text-transform: capitalize; } + + .reading-section { max-height: 100vh; width: 100%; //overflow: auto; // This will break progress reporting + height: 100vh; } .reader-container { @@ -166,6 +108,40 @@ $primary-color: #0062cc; .book-content { position: relative; + padding-top: 20px; + padding-bottom: 20px; + margin: 0px 0px; + height: calc(var(--vh)*100); // This will ensure bottom bar extends to the bottom of the screen + + a, :link { + color: var(--brtheme-link-text-color); + } + + background-color: var(--brtheme-bg-color); +} + + + +// This is essentially fitting the text to height and when you press next you are scrolling over by page width +.column-layout-1 { + .book-content { + column-count: 1; + column-gap: 20px; + overflow: hidden; + word-break: break-word; + overflow-wrap: break-word; + } +} + +.column-layout-2 { + .book-content { + column-count: 2; + column-gap: 20px; + overflow: hidden; + word-break: break-word; + overflow-wrap: break-word; + } + } // A bunch of resets so books render correctly @@ -175,18 +151,15 @@ $primary-color: #0062cc; } } -.drawer-body { - padding-bottom: 20px; -} -.chapter-title { - padding-inline-start: 0px -} - -::ng-deep .scale-width { +// This is applied to images in the backend +::ng-deep .kavita-scale-width { max-width: 100%; object-fit: contain; object-position: top center; + break-inside: avoid; + break-before: column; + max-height: 100vh; } @@ -195,9 +168,8 @@ $primary-color: #0062cc; color: $primary-color; } -.dark-mode .overlay { - opacity: 0; -} + + .right { @@ -246,14 +218,17 @@ $primary-color: #0062cc; animation: fadein .5s both; } + + + .btn { &.btn-secondary { - color: #6c757d; + color: var(--br-actionbar-button-text-color); border-color: transparent; background-color: unset; &:hover, &:focus { - border-color: #545b62; + border-color: var(--br-actionbar-button-hover-border-color); } } @@ -262,18 +237,18 @@ $primary-color: #0062cc; background-color: unset; &:hover, &:focus { - border-color: #545b62; + border-color: var(--br-actionbar-button-hover-border-color); // #545b62; } } span { background-color: unset; - color: #6c757d; + color: var(--br-actionbar-button-text-color); // #6c757d; } i { background-color: unset; - color: #6c757d; + color: var(--br-actionbar-button-text-color); } &:active { diff --git a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts index bf501d3b2..9a235dde1 100644 --- a/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts +++ b/UI/Web/src/app/book-reader/book-reader/book-reader.component.ts @@ -1,18 +1,15 @@ import { AfterViewInit, Component, ElementRef, HostListener, Inject, OnDestroy, OnInit, Renderer2, RendererStyleFlags2, ViewChild } from '@angular/core'; import {DOCUMENT, Location} from '@angular/common'; -import { FormControl, FormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; import { forkJoin, fromEvent, Subject } from 'rxjs'; -import { debounceTime, take, takeUntil } from 'rxjs/operators'; +import { debounceTime, filter, take, takeUntil, tap } from 'rxjs/operators'; import { Chapter } from 'src/app/_models/chapter'; -import { User } from 'src/app/_models/user'; import { AccountService } from 'src/app/_services/account.service'; import { NavService } from 'src/app/_services/nav.service'; import { ReaderService } from 'src/app/_services/reader.service'; import { SeriesService } from 'src/app/_services/series.service'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; - import { BookService } from '../book.service'; import { KEY_CODES, UtilityService } from 'src/app/shared/_services/utility.service'; import { BookChapterItem } from '../_models/book-chapter-item'; @@ -23,16 +20,19 @@ import { ReadingDirection } from 'src/app/_models/preferences/reading-direction' import { MangaFormat } from 'src/app/_models/manga-format'; import { LibraryService } from 'src/app/_services/library.service'; import { LibraryType } from 'src/app/_models/library'; +import { BookTheme } from 'src/app/_models/preferences/book-theme'; +import { BookPageLayoutMode } from 'src/app/_models/book-page-layout-mode'; +import { PageStyle } from '../reader-settings/reader-settings.component'; +import { User } from 'src/app/_models/user'; +import { LayoutMode } from 'src/app/manga-reader/_models/layout-mode'; import { ThemeService } from 'src/app/_services/theme.service'; import { ScrollService } from 'src/app/_services/scroll.service'; +import { PAGING_DIRECTION } from 'src/app/manga-reader/_models/reader-enums'; -interface PageStyle { - 'font-family': string; - 'font-size': string; - 'line-height': string; - 'margin-left': string; - 'margin-right': string; +enum TabID { + Settings = 1, + TableOfContents = 2 } interface HistoryPoint { @@ -86,31 +86,44 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { * If this is true, no progress will be saved. */ incognitoMode: boolean = false; - + /** - * If this is true, chapters will be fetched in the order of a reading list, rather than natural series order. + * If this is true, chapters will be fetched in the order of a reading list, rather than natural series order. */ readingListMode: boolean = false; + /** + * The actual pages from the epub, used for showing on table of contents. This must be here as we need access to it for scroll anchors + */ chapters: Array = []; - + /** + * Current Page + */ pageNum = 0; + /** + * Max Pages + */ maxPages = 1; + /** + * This allows for exploration into different chapters + */ adhocPageHistory: Stack = new Stack(); /** * A stack of the chapter ids we come across during continuous reading mode. When we traverse a boundary, we use this to avoid extra API calls. * @see Stack */ - continuousChaptersStack: Stack = new Stack(); - - user!: User; + continuousChaptersStack: Stack = new Stack(); // TODO: See if this can be moved into reader service so we can reduce code duplication between readers + + activeTabId: TabID = TabID.Settings; drawerOpen = false; - isLoading = true; + isLoading = true; bookTitle: string = ''; - settingsForm: FormGroup = new FormGroup({}); - clickToPaginate = false; + clickToPaginate = false; + /** + * The boolean that decides if the clickToPaginate overlay is visible or not. + */ clickToPaginateVisualOverlay = false; clickToPaginateVisualOverlayTimeout: any = undefined; // For animation clickToPaginateVisualOverlayTimeout2: any = undefined; // For kicking off animation, giving enough time to render html @@ -157,20 +170,17 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { nextPageDisabled = false; /** - * Internal property used to capture all the different css properties to render on all elements + * Internal property used to capture all the different css properties to render on all elements. This is a cached version that is updated from reader-settings component */ pageStyles!: PageStyle; - /** - * List of all font families user can select from - */ - fontFamilies: Array = []; - - darkMode = false; + + darkMode = true; backgroundColor: string = 'white'; - readerStyles: string = ''; - darkModeStyleElem!: HTMLElement; - topOffset: number = 0; // Offset for drawer and rendering canvas + /** + * Offset for drawer and rendering canvas. Fixed to 62px. + */ + topOffset: number = 62; /** * Used for showing/hiding bottom action bar. Calculates if there is enough scroll to show it. * Will hide if all content in book is absolute positioned @@ -191,35 +201,39 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { */ libraryType: LibraryType = LibraryType.Book; - /** - * Hack: Override background color for reader and restore it onDestroy - */ - originalBodyColor: string | undefined; - /** * If the web browser is in fullscreen mode */ isFullscreen: boolean = false; - darkModeStyles = ` - *:not(input), *:not(select), *:not(code), *:not(:link), *:not(.ngx-toastr) { - color: #dcdcdc !important; - } + /** + * How to render the page content + */ + layoutMode: BookPageLayoutMode = BookPageLayoutMode.Default; - code { - color: #e83e8c !important; - } - :link, a { - color: #8db2e5 !important; - } + /** + * Width of the document (in non-column layout), used for column layout virtual paging + */ + windowWidth: number = 0; + windowHeight: number = 0; - img, img[src] { - z-index: 1; - filter: brightness(0.85) !important; - background-color: initial !important; - } - `; + user!: User; + + /** + * Used to keep track of direction user is paging, to help with virtual paging on column layout + */ + pagingDirection: PAGING_DIRECTION = PAGING_DIRECTION.FORWARD; + + + + get BookPageLayoutMode() { + return BookPageLayoutMode; + } + + get TabID(): typeof TabID { + return TabID; + } get ReadingDirection(): typeof ReadingDirection { return ReadingDirection; @@ -252,122 +266,105 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { return this.pageNum === 0; } - get drawerBackgroundColor() { - return this.darkMode ? '#010409': '#fff'; + get ColumnWidth() { + switch (this.layoutMode) { + case BookPageLayoutMode.Default: + return 'unset'; + case BookPageLayoutMode.Column1: + return (this.windowWidth /2) + 'px'; + case BookPageLayoutMode.Column2: + return ((this.windowWidth / 4)) + 'px'; + } } + get ColumnHeight() { + if (this.layoutMode !== BookPageLayoutMode.Default) { + // Take the height after page loads, subtract the top/bottom bar + return this.windowHeight - (this.topOffset *2) + 'px'; + } + return 'unset'; + } + + get ColumnLayout() { + switch (this.layoutMode) { + case BookPageLayoutMode.Default: + return ''; + case BookPageLayoutMode.Column1: + return 'column-layout-1'; + case BookPageLayoutMode.Column2: + return 'column-layout-2'; + } + } + + constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private seriesService: SeriesService, private readerService: ReaderService, private location: Location, - private renderer: Renderer2, private navService: NavService, private toastr: ToastrService, + private renderer: Renderer2, private navService: NavService, private toastr: ToastrService, private domSanitizer: DomSanitizer, private bookService: BookService, private memberService: MemberService, private scrollService: ScrollService, private utilityService: UtilityService, private libraryService: LibraryService, @Inject(DOCUMENT) private document: Document, private themeService: ThemeService) { this.navService.hideNavBar(); + this.themeService.clearThemes(); this.navService.hideSideNav(); - - this.darkModeStyleElem = this.renderer.createElement('style'); - this.darkModeStyleElem.innerHTML = this.darkModeStyles; - this.fontFamilies = this.bookService.getFontFamilies(); - - this.accountService.currentUser$.pipe(take(1)).subscribe(user => { - if (user) { - this.user = user; - - if (this.user.preferences.bookReaderFontFamily === undefined) { - this.user.preferences.bookReaderFontFamily = 'default'; - } - if (this.user.preferences.bookReaderFontSize === undefined) { - this.user.preferences.bookReaderFontSize = 100; - } - if (this.user.preferences.bookReaderLineSpacing === undefined) { - this.user.preferences.bookReaderLineSpacing = 100; - } - if (this.user.preferences.bookReaderMargin === undefined) { - this.user.preferences.bookReaderMargin = 0; - } - if (this.user.preferences.bookReaderReadingDirection === undefined) { - this.user.preferences.bookReaderReadingDirection = ReadingDirection.LeftToRight; - } - - this.readingDirection = this.user.preferences.bookReaderReadingDirection; - - this.clickToPaginate = this.user.preferences.bookReaderTapToPaginate; - - this.settingsForm.addControl('bookReaderFontFamily', new FormControl(user.preferences.bookReaderFontFamily, [])); - - this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(changes => { - this.updateFontFamily(changes); - }); - } - - const bodyNode = this.document.querySelector('body'); - if (bodyNode !== undefined && bodyNode !== null) { - this.originalBodyColor = bodyNode.style.background; - } - this.resetSettings(); - }); } /** - * After the page has loaded, setup the scroll handler. The scroll handler has 2 parts. One is if there are page anchors setup (aka page anchor elements linked with the - * table of content) then we calculate what has already been reached and grab the last reached one to save progress. If page anchors aren't setup (toc missing), then try to save progress + * After the page has loaded, setup the scroll handler. The scroll handler has 2 parts. One is if there are page anchors setup (aka page anchor elements linked with the + * table of content) then we calculate what has already been reached and grab the last reached one to save progress. If page anchors aren't setup (toc missing), then try to save progress * based on the last seen scroll part (xpath). */ ngAfterViewInit() { // check scroll offset and if offset is after any of the "id" markers, save progress fromEvent(this.reader.nativeElement, 'scroll') - .pipe(debounceTime(200), takeUntil(this.onDestroy)).subscribe((event) => { + .pipe( + debounceTime(200), + takeUntil(this.onDestroy)) + .subscribe((event) => { if (this.isLoading) return; - // Highlight the current chapter we are on - if (Object.keys(this.pageAnchors).length !== 0) { - // get the height of the document so we can capture markers that are halfway on the document viewport - const verticalOffset = this.scrollService.scrollPosition + (this.document.body.offsetHeight / 2); - - const alreadyReached = Object.values(this.pageAnchors).filter((i: number) => i <= verticalOffset); - if (alreadyReached.length > 0) { - this.currentPageAnchor = Object.keys(this.pageAnchors)[alreadyReached.length - 1]; - } else { - this.currentPageAnchor = ''; - } - } - - - // Find the element that is on screen to bookmark against - const intersectingEntries = Array.from(this.readingSectionElemRef.nativeElement.querySelectorAll('div,o,p,ul,li,a,img,h1,h2,h3,h4,h5,h6,span')) - .filter(element => !element.classList.contains('no-observe')) - .filter(entry => { - return this.utilityService.isInViewport(entry, this.topOffset); - }); - - intersectingEntries.sort((a: Element, b: Element) => { - const aTop = a.getBoundingClientRect().top; - const bTop = b.getBoundingClientRect().top; - if (aTop < bTop) { - return -1; - } - if (aTop > bTop) { - return 1; - } - - return 0; - }); - - if (intersectingEntries.length > 0) { - let path = this.getXPathTo(intersectingEntries[0]); - if (path === '') { return; } - if (!path.startsWith('id')) { - path = '//html[1]/' + path; - } - this.lastSeenScrollPartPath = path; - } - - if (this.lastSeenScrollPartPath !== '') { - this.saveProgress(); - } + this.handleScrollEvent(); }); } + handleScrollEvent() { + // Highlight the current chapter we are on + if (Object.keys(this.pageAnchors).length !== 0) { + // get the height of the document so we can capture markers that are halfway on the document viewport + const verticalOffset = this.scrollService.scrollPosition + (this.document.body.offsetHeight / 2); + + const alreadyReached = Object.values(this.pageAnchors).filter((i: number) => i <= verticalOffset); + if (alreadyReached.length > 0) { + this.currentPageAnchor = Object.keys(this.pageAnchors)[alreadyReached.length - 1]; + } else { + this.currentPageAnchor = ''; + } + } + + + // Find the element that is on screen to bookmark against + + const intersectingEntries = Array.from(this.readingHtml.nativeElement.querySelectorAll('div,o,p,ul,li,a,img,h1,h2,h3,h4,h5,h6,span')) + .filter(element => !element.classList.contains('no-observe')) + .filter(entry => { + return this.utilityService.isInViewport(entry, this.topOffset); + }); + + intersectingEntries.sort(this.sortElements); + + if (intersectingEntries.length > 0) { + let path = this.getXPathTo(intersectingEntries[0]); + if (path === '') { return; } + if (!path.startsWith('id')) { + path = '//html[1]/' + path; + } + this.lastSeenScrollPartPath = path; + } + + if (this.lastSeenScrollPartPath !== '') { + this.saveProgress(); + } + } + saveProgress() { let tempPageNum = this.pageNum; if (this.pageNum == this.maxPages - 1) { @@ -381,28 +378,17 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } ngOnDestroy(): void { - const bodyNode = this.document.querySelector('body'); - if (bodyNode !== undefined && bodyNode !== null && this.originalBodyColor !== undefined) { - bodyNode.style.background = this.originalBodyColor; - this.themeService.currentTheme$.pipe(take(1)).subscribe(theme => { - this.themeService.setTheme(theme.name); - }); - } + this.clearTimeout(this.clickToPaginateVisualOverlayTimeout); + this.clearTimeout(this.clickToPaginateVisualOverlayTimeout2); + + this.themeService.clearBookTheme(); + + this.themeService.currentTheme$.pipe(take(1)).subscribe(theme => { + this.themeService.setTheme(theme.name); + }); + this.navService.showNavBar(); this.navService.showSideNav(); - - const head = this.document.querySelector('head'); - this.renderer.removeChild(head, this.darkModeStyleElem); - - if (this.clickToPaginateVisualOverlayTimeout !== undefined) { - clearTimeout(this.clickToPaginateVisualOverlayTimeout); - this.clickToPaginateVisualOverlayTimeout = undefined; - } - if (this.clickToPaginateVisualOverlayTimeout2 !== undefined) { - clearTimeout(this.clickToPaginateVisualOverlayTimeout2); - this.clickToPaginateVisualOverlayTimeout2 = undefined; - } - this.readerService.exitFullscreen(); this.onDestroy.next(); @@ -439,7 +425,12 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } }); - this.init(); + this.accountService.currentUser$.pipe(take(1)).subscribe(user => { + if (user) { + this.user = user; + this.init(); + } + }); } init() { @@ -449,11 +440,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.prevChapterDisabled = false; this.nextChapterPrefetched = false; + + this.bookService.getBookInfo(this.chapterId).subscribe(info => { this.bookTitle = info.bookTitle; - + if (this.readingListMode && info.seriesFormat !== MangaFormat.EPUB) { - // Redirect to the manga reader. + // Redirect to the manga reader. const params = this.readerService.getQueryParamsObject(this.incognitoMode, this.readingListMode, this.readingListId); this.router.navigate(['library', info.libraryId, 'series', info.seriesId, 'manga', this.chapterId], {queryParams: params}); return; @@ -463,27 +456,31 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { chapter: this.seriesService.getChapter(this.chapterId), progress: this.readerService.getProgress(this.chapterId), chapters: this.bookService.getBookChapters(this.chapterId), - }).pipe(take(1)).subscribe(results => { + }).subscribe(results => { this.chapter = results.chapter; this.volumeId = results.chapter.volumeId; this.maxPages = results.chapter.pages; this.chapters = results.chapters; this.pageNum = results.progress.pageNum; - - + if (results.progress.bookScrollId) this.lastSeenScrollPartPath = results.progress.bookScrollId; + + + this.continuousChaptersStack.push(this.chapterId); this.libraryService.getLibraryType(this.libraryId).pipe(take(1)).subscribe(type => { this.libraryType = type; }); - - - + + this.updateLayoutMode(this.user.preferences.bookReaderLayoutMode || BookPageLayoutMode.Default); + + + if (this.pageNum >= this.maxPages) { this.pageNum = this.maxPages - 1; this.saveProgress(); } - + this.readerService.getNextChapter(this.seriesId, this.volumeId, this.chapterId, this.readingListId).pipe(take(1)).subscribe(chapterId => { this.nextChapterId = chapterId; if (chapterId === CHAPTER_ID_DOESNT_EXIST || chapterId === this.chapterId) { @@ -496,7 +493,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.prevChapterDisabled = true; } }); - + // Check if user progress has part, if so load it so we scroll to it this.loadPage(results.progress.bookScrollId || undefined); }, () => { @@ -505,8 +502,18 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { }, 200); }); }); + } - + @HostListener('window:resize', ['$event']) + onResize(event: any){ + // Update the window Height + this.windowHeight = Math.max(this.readingSectionElemRef.nativeElement.clientHeight, window.innerHeight); + } + + @HostListener('window:orientationchange', ['$event']) + onOrientationChange() { + // Update the window Height + this.windowHeight = Math.max(this.readingSectionElemRef.nativeElement.clientHeight, window.innerHeight); } @HostListener('window:keydown', ['$event']) @@ -520,7 +527,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } else if (event.key === KEY_CODES.SPACE) { this.toggleDrawer(); event.stopPropagation(); - event.preventDefault(); + event.preventDefault(); } else if (event.key === KEY_CODES.G) { this.goToPage(); } else if (event.key === KEY_CODES.F) { @@ -580,7 +587,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { loadChapter(chapterId: number, direction: 'Next' | 'Prev') { if (chapterId >= 0) { this.chapterId = chapterId; - this.continuousChaptersStack.push(chapterId); + this.continuousChaptersStack.push(chapterId); // Load chapter Id onto route but don't reload const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId); window.history.replaceState({}, '', newRoute); @@ -598,9 +605,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } - loadChapterPage(pageNum: number, part: string) { - this.setPageNum(pageNum); - this.loadPage('id("' + part + '")'); + loadChapterPage(event: {pageNum: number, part: string}) { + this.setPageNum(event.pageNum); + this.loadPage('id("' + event.part + '")'); } closeReader() { @@ -611,34 +618,10 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } - resetSettings() { - const windowWidth = window.innerWidth - || this.document.documentElement.clientWidth - || this.document.body.clientWidth; - - let margin = '15%'; - if (windowWidth <= 700) { - margin = '5%'; - } - if (this.user) { - if (windowWidth > 700) { - margin = this.user.preferences.bookReaderMargin + '%'; - } - this.pageStyles = {'font-family': this.user.preferences.bookReaderFontFamily, 'font-size': this.user.preferences.bookReaderFontSize + '%', 'margin-left': margin, 'margin-right': margin, 'line-height': this.user.preferences.bookReaderLineSpacing + '%'}; - - this.toggleDarkMode(this.user.preferences.bookReaderDarkMode); - } else { - this.pageStyles = {'font-family': 'default', 'font-size': '100%', 'margin-left': margin, 'margin-right': margin, 'line-height': '100%'}; - this.toggleDarkMode(false); - } - - this.settingsForm.get('bookReaderFontFamily')?.setValue(this.user.preferences.bookReaderFontFamily); - this.updateReaderStyles(); - } /** - * Adds a click handler for any anchors that have 'kavita-page'. If 'kavita-page' present, changes page to kavita-page and optionally passes a part value - * from 'kavita-part', which will cause the reader to scroll to the marker. + * Adds a click handler for any anchors that have 'kavita-page'. If 'kavita-page' present, changes page to kavita-page and optionally passes a part value + * from 'kavita-part', which will cause the reader to scroll to the marker. */ addLinkClickHandlers() { var links = this.readingSectionElemRef.nativeElement.querySelectorAll('a'); @@ -649,13 +632,13 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { if (this.adhocPageHistory.peek()?.page !== this.pageNum) { this.adhocPageHistory.push({page: this.pageNum, scrollOffset: window.pageYOffset}); } - + var partValue = e.target.attributes.hasOwnProperty('kavita-part') ? e.target.attributes['kavita-part'].value : undefined; if (partValue && page === this.pageNum) { this.scrollTo(e.target.attributes['kavita-part'].value); return; } - + this.setPageNum(page); this.loadPage(partValue); }); @@ -669,6 +652,7 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } + promptForPage() { const question = 'There are ' + (this.maxPages - 1) + ' pages. What page do you want to go to?'; const goToPageNum = window.prompt(question, ''); @@ -698,9 +682,358 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.pageNum = page; this.loadPage(); + } + + + + + loadPage(part?: string | undefined, scrollTop?: number | undefined) { + this.isLoading = true; + + this.bookService.getBookPage(this.chapterId, this.pageNum).pipe(take(1)).subscribe(content => { + this.page = this.domSanitizer.bypassSecurityTrustHtml(content); // PERF: Potential optimization to prefetch next/prev page and store in localStorage + + setTimeout(() => { + this.addLinkClickHandlers(); + this.updateReaderStyles(this.pageStyles); + this.updateReaderStyles(this.pageStyles); + + const imgs = this.readingSectionElemRef.nativeElement.querySelectorAll('img'); + if (imgs === null || imgs.length === 0) { + this.setupPage(part, scrollTop); + return; + } + + Promise.all(Array.from(imgs) + .filter(img => !img.complete) + .map(img => new Promise(resolve => { img.onload = img.onerror = resolve; }))) + .then(() => { + this.setupPage(part, scrollTop); + this.updateImagesWithHeight(); + }); + }, 10); + }); + } + + /** + * Applies a max-height inline css property on each image in the page if the layout mode is column-based, else it removes the property + */ + updateImagesWithHeight() { + const images = this.readingSectionElemRef.nativeElement.querySelectorAll('img') || []; + + if (this.layoutMode !== BookPageLayoutMode.Default) { + const height = this.ColumnHeight; + Array.from(images).forEach(img => { + this.renderer.setStyle(img, 'max-height', height); + }); + } else { + Array.from(images).forEach(img => { + this.renderer.removeStyle(img, 'max-height'); + }); + } + } + + setupPage(part?: string | undefined, scrollTop?: number | undefined) { + this.isLoading = false; + this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.readingSectionElemRef.nativeElement.clientHeight; + + // Virtual Paging stuff + this.windowWidth = window.innerWidth + || this.document.documentElement.clientWidth + || this.document.body.clientWidth; + + this.windowHeight = Math.max(this.readingSectionElemRef.nativeElement.clientHeight, this.windowHeight); + this.updateLayoutMode(this.layoutMode || BookPageLayoutMode.Default); + + // Find all the part ids and their top offset + this.setupPageAnchors(); + + + if (part !== undefined && part !== '') { + this.scrollTo(part); + } else if (scrollTop !== undefined && scrollTop !== 0) { + this.scrollService.scrollTo(scrollTop, this.reader.nativeElement); + } else { + + if (this.layoutMode === BookPageLayoutMode.Default) { + this.scrollService.scrollTo(0, this.reader.nativeElement); + } else { + this.reader.nativeElement.children + // We need to check if we are paging back, because we need to adjust the scroll + if (this.pagingDirection === PAGING_DIRECTION.BACKWARDS) { + this.scrollService.scrollToX(this.readingHtml.nativeElement.scrollWidth, this.readingHtml.nativeElement); + } else { + this.scrollService.scrollToX(0, this.readingHtml.nativeElement); + } + } + } + + // we need to click the document before arrow keys will scroll down. + this.reader.nativeElement.focus(); + this.saveProgress(); + } + + + goBack() { + if (!this.adhocPageHistory.isEmpty()) { + const page = this.adhocPageHistory.pop(); + if (page !== undefined) { + this.setPageNum(page.page); + this.loadPage(undefined, page.scrollOffset); + } + } + } + + setPageNum(pageNum: number) { + this.pageNum = Math.max(Math.min(pageNum, this.maxPages), 0); + + if (this.pageNum >= this.maxPages - 10) { + // Tell server to cache the next chapter + if (this.nextChapterId > 0 && !this.nextChapterPrefetched) { + this.readerService.getChapterInfo(this.nextChapterId).pipe(take(1)).subscribe(res => { + this.nextChapterPrefetched = true; + }); + } + } else if (this.pageNum <= 10) { + if (this.prevChapterId > 0 && !this.prevChapterPrefetched) { + this.readerService.getChapterInfo(this.prevChapterId).pipe(take(1)).subscribe(res => { + this.prevChapterPrefetched = true; + }); + } + } + } + + prevPage() { + const oldPageNum = this.pageNum; + + this.pagingDirection = PAGING_DIRECTION.BACKWARDS; + + // We need to handle virtual paging before we increment the actual page + if (this.layoutMode !== BookPageLayoutMode.Default) { + + const scrollOffset = this.readingHtml.nativeElement.scrollLeft; + const pageWidth = this.readingSectionElemRef.nativeElement.clientWidth - (this.readingSectionElemRef.nativeElement.clientWidth*(parseInt(this.pageStyles['margin-left'], 10) / 100))*2 + 20; + + if (scrollOffset - pageWidth >= 0) { + this.scrollService.scrollToX(scrollOffset - pageWidth, this.readingHtml.nativeElement); + this.saveProgress(); + return; + } + } + + if (this.readingDirection === ReadingDirection.LeftToRight) { + this.setPageNum(this.pageNum - 1); + } else { + this.setPageNum(this.pageNum + 1); + } + + if (oldPageNum === 0) { + // Move to next volume/chapter automatically + this.loadPrevChapter(); + return; + } + + if (oldPageNum === this.pageNum) { return; } + + // If prev and in default layout, need to handle somehow + + this.loadPage(); + } + + nextPage(event?: any) { + if (event) { + event.stopPropagation(); + event.preventDefault(); + } + + this.pagingDirection = PAGING_DIRECTION.FORWARD; + + // We need to handle virtual paging before we increment the actual page + if (this.layoutMode !== BookPageLayoutMode.Default) { + + const scrollOffset = this.readingHtml.nativeElement.scrollLeft; + const totalScroll = this.readingHtml.nativeElement.scrollWidth; + const pageWidth = this.readingSectionElemRef.nativeElement.clientWidth - (this.readingSectionElemRef.nativeElement.clientWidth*(parseInt(this.pageStyles['margin-left'], 10) / 100))*2 + 20; + + + if (scrollOffset + pageWidth < totalScroll) { + this.scrollService.scrollToX(scrollOffset + pageWidth, this.readingHtml.nativeElement); + this.handleScrollEvent(); + this.saveProgress(); + return; + } + } + + const oldPageNum = this.pageNum; + if (oldPageNum + 1 === this.maxPages) { + // Move to next volume/chapter automatically + this.loadNextChapter(); + return; + } + + + if (this.readingDirection === ReadingDirection.LeftToRight) { + this.setPageNum(this.pageNum + 1); + } else { + this.setPageNum(this.pageNum - 1); + } + + if (oldPageNum === this.pageNum) { return; } + + this.loadPage(); + } + + /** + * Applies styles onto the html of the book page + */ + updateReaderStyles(pageStyles: PageStyle) { + this.pageStyles = pageStyles; + if (this.readingHtml === undefined || !this.readingHtml.nativeElement) return; + + // Line Height must be placed on each element in the page + + // Apply page level overrides + Object.entries(this.pageStyles).forEach(item => { + if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') { + // Remove the style or skip + this.renderer.removeStyle(this.readingHtml.nativeElement, item[0]); + return; + } + if (pageLevelStyles.includes(item[0])) { + this.renderer.setStyle(this.readingHtml.nativeElement, item[0], item[1], RendererStyleFlags2.Important); + } + }); + + const individualElementStyles = Object.entries(this.pageStyles).filter(item => elementLevelStyles.includes(item[0])); + for(let i = 0; i < this.readingHtml.nativeElement.children.length; i++) { + const elem = this.readingHtml.nativeElement.children.item(i); + if (elem?.tagName === 'STYLE') continue; + individualElementStyles.forEach(item => { + if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') { + // Remove the style or skip + this.renderer.removeStyle(elem, item[0]); + return; + } + this.renderer.setStyle(elem, item[0], item[1], RendererStyleFlags2.Important); + }); + + } } + setOverrideStyles(theme: BookTheme) { + // TODO: Put optimization in to avoid any work if the theme is the same as selected (or have reading settings control handle that) + + // Remove all themes + Array.from(this.document.querySelectorAll('style[id^="brtheme-"]')).forEach(elem => elem.remove()); + + this.darkMode = theme.isDarkTheme; + + const styleElem = this.renderer.createElement('style'); + styleElem.id = theme.selector; + styleElem.innerHTML = theme.content; + + + this.renderer.appendChild(this.document.querySelector('.reading-section'), styleElem); + // I need to also apply the selector onto the body so that any css variables will take effect + this.themeService.setBookTheme(theme.selector); + } + + toggleDrawer() { + this.drawerOpen = !this.drawerOpen; + } + + scrollTo(partSelector: string) { + if (partSelector.startsWith('#')) { + partSelector = partSelector.substr(1, partSelector.length); + } + + let element: Element | null = null; + if (partSelector.startsWith('//') || partSelector.startsWith('id(')) { + // Part selector is a XPATH + element = this.getElementFromXPath(partSelector); + } else { + element = this.document.querySelector('*[id="' + partSelector + '"]'); + } + + if (element === null) return; + + if (this.layoutMode === BookPageLayoutMode.Default) { + const fromTopOffset = element.getBoundingClientRect().top + window.pageYOffset + TOP_OFFSET; + // We need to use a delay as webkit browsers (aka apple devices) don't always have the document rendered by this point + setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), 10); + } else { + setTimeout(() => (element as Element).scrollIntoView({'block': 'start', 'inline': 'start'})); + } + } + + + getElementFromXPath(path: string) { + const node = this.document.evaluate(path, this.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; + if (node?.nodeType === Node.ELEMENT_NODE) { + return node as Element; + } + return null; + } + + getXPathTo(element: any): string { + if (element === null) return ''; + if (element.id !== '') { return 'id("' + element.id + '")'; } + if (element === this.document.body) { return element.tagName; } + + + let ix = 0; + const siblings = element.parentNode?.childNodes || []; + for (let sibling of siblings) { + if (sibling === element) { + return this.getXPathTo(element.parentNode) + '/' + element.tagName + '[' + (ix + 1) + ']'; + } + if (sibling.nodeType === 1 && sibling.tagName === element.tagName) { + ix++; + } + + } + return ''; + } + + /** + * Turns off Incognito mode. This can only happen once if the user clicks the icon. This will modify URL state + */ + turnOffIncognito() { + this.incognitoMode = false; + const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId); + window.history.replaceState({}, '', newRoute); + this.toastr.info('Incognito mode is off. Progress will now start being tracked.'); + this.saveProgress(); + } + + toggleFullscreen() { + this.isFullscreen = this.readerService.checkFullscreenMode(); + if (this.isFullscreen) { + this.readerService.exitFullscreen(() => { + this.isFullscreen = false; + this.renderer.removeStyle(this.reader.nativeElement, 'background'); + }); + } else { + this.readerService.enterFullscreen(this.reader.nativeElement, () => { + this.isFullscreen = true; + // HACK: This is a bug with how browsers change the background color for fullscreen mode + this.renderer.setStyle(this.reader.nativeElement, 'background', this.themeService.getCssVariable('--bs-body-color')); + if (!this.darkMode) { + this.renderer.setStyle(this.reader.nativeElement, 'background', 'white'); + } + }); + } + } + + updateLayoutMode(mode: BookPageLayoutMode) { + this.layoutMode = mode; + + // Remove any max-heights from column layout + this.updateImagesWithHeight(); + } + + // Table of Contents cleanIdSelector(id: string) { const tokens = id.split('/'); if (tokens.length > 0) { @@ -730,308 +1063,29 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } } - loadPage(part?: string | undefined, scrollTop?: number | undefined) { - this.isLoading = true; + // Settings Handlers + showPaginationOverlay(clickToPaginate: boolean) { + this.clickToPaginate = clickToPaginate; - this.saveProgress(); - - this.bookService.getBookPage(this.chapterId, this.pageNum).pipe(take(1)).subscribe(content => { - this.page = this.domSanitizer.bypassSecurityTrustHtml(content); - setTimeout(() => { - this.addLinkClickHandlers(); - this.updateReaderStyles(); - // We need to get the offset after we ensure the title has rendered - requestAnimationFrame(() => this.topOffset = this.stickyTopElemRef.nativeElement?.getBoundingClientRect().height); - - const imgs = this.readingSectionElemRef.nativeElement.querySelectorAll('img'); - if (imgs === null || imgs.length === 0) { - this.setupPage(part, scrollTop); - return; - } - - // Apply scaling class to all images to ensure they scale down to max width to not blow out the reader - Array.from(imgs).forEach(img => this.renderer.addClass(img, 'scale-width')); - - Promise.all(Array.from(imgs) - .filter(img => !img.complete) - .map(img => new Promise(resolve => { img.onload = img.onerror = resolve; }))) - .then(() => { - this.setupPage(part, scrollTop); - }); - }, 10); - }); - } - - setupPage(part?: string | undefined, scrollTop?: number | undefined) { - this.isLoading = false; - this.scrollbarNeeded = this.readingHtml.nativeElement.clientHeight > this.readingSectionElemRef.nativeElement.clientHeight; - - // Find all the part ids and their top offset - this.setupPageAnchors(); - - - if (part !== undefined && part !== '') { - this.scrollTo(part); - } else if (scrollTop !== undefined && scrollTop !== 0) { - this.scrollService.scrollTo(scrollTop, this.reader.nativeElement); - } else { - this.scrollService.scrollTo(0, this.reader.nativeElement); - } - - // we need to click the document before arrow keys will scroll down. - this.reader.nativeElement.focus(); - } - - setPageNum(pageNum: number) { - this.pageNum = Math.max(Math.min(pageNum, this.maxPages), 0); - } - - goBack() { - if (!this.adhocPageHistory.isEmpty()) { - const page = this.adhocPageHistory.pop(); - if (page !== undefined) { - this.setPageNum(page.page); - this.loadPage(undefined, page.scrollOffset); - } - } - } - - clickOverlayClass(side: 'right' | 'left') { - if (!this.clickToPaginateVisualOverlay) { - return ''; - } - - if (this.readingDirection === ReadingDirection.LeftToRight) { - return side === 'right' ? 'highlight' : 'highlight-2'; - } - return side === 'right' ? 'highlight-2' : 'highlight'; - } - - prevPage() { - const oldPageNum = this.pageNum; - - if (this.readingDirection === ReadingDirection.LeftToRight) { - this.setPageNum(this.pageNum - 1); - } else { - this.setPageNum(this.pageNum + 1); - } - - if (oldPageNum === 0) { - // Move to next volume/chapter automatically - this.loadPrevChapter(); - return; - } - - if (oldPageNum === this.pageNum) { return; } - - this.loadPage(); - } - - nextPage(event?: any) { - if (event) { - event.stopPropagation(); - event.preventDefault(); - } - const oldPageNum = this.pageNum; - if (oldPageNum + 1 === this.maxPages) { - // Move to next volume/chapter automatically - this.loadNextChapter(); - return; - } - - - if (this.readingDirection === ReadingDirection.LeftToRight) { - this.setPageNum(this.pageNum + 1); - } else { - this.setPageNum(this.pageNum - 1); - } - - - - if (oldPageNum === this.pageNum) { return; } - - - this.loadPage(); - } - - updateFontSize(amount: number) { - let val = parseInt(this.pageStyles['font-size'].substr(0, this.pageStyles['font-size'].length - 1), 10); - - if (val + amount > 300 || val + amount < 50) { - return; - } - - this.pageStyles['font-size'] = val + amount + '%'; - this.updateReaderStyles(); - } - - updateFontFamily(familyName: string) { - if (familyName === null) familyName = ''; - let cleanedName = familyName.replace(' ', '_').replace('!important', '').trim(); - if (cleanedName === 'default') { - this.pageStyles['font-family'] = 'inherit'; - } else { - this.pageStyles['font-family'] = "'" + cleanedName + "'"; - } - - this.updateReaderStyles(); - } - - updateMargin(amount: number) { - let cleanedValue = this.pageStyles['margin-left'].replace('%', '').replace('!important', '').trim(); - let val = parseInt(cleanedValue, 10); - - if (val + amount > 30 || val + amount < 0) { - return; - } - - this.pageStyles['margin-left'] = (val + amount) + '%'; - this.pageStyles['margin-right'] = (val + amount) + '%'; - - this.updateReaderStyles(); - } - - updateLineSpacing(amount: number) { - const cleanedValue = parseInt(this.pageStyles['line-height'].replace('%', '').replace('!important', '').trim(), 10); - - if (cleanedValue + amount > 250 || cleanedValue + amount < 100) { - return; - } - - this.pageStyles['line-height'] = (cleanedValue + amount) + '%'; - - this.updateReaderStyles(); - } - - /** - * Applies styles onto the html of the book page - */ - updateReaderStyles() { - if (this.readingHtml === undefined || !this.readingHtml.nativeElement) return; - - // Line Height must be placed on each element in the page - - // Apply page level overrides - Object.entries(this.pageStyles).forEach(item => { - if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') { - // Remove the style or skip - this.renderer.removeStyle(this.readingHtml.nativeElement, item[0]); - return; - } - if (pageLevelStyles.includes(item[0])) { - this.renderer.setStyle(this.readingHtml.nativeElement, item[0], item[1], RendererStyleFlags2.Important); - } - }); - - const individualElementStyles = Object.entries(this.pageStyles).filter(item => elementLevelStyles.includes(item[0])); - for(let i = 0; i < this.readingHtml.nativeElement.children.length; i++) { - const elem = this.readingHtml.nativeElement.children.item(i); - if (elem?.tagName === 'STYLE') continue; - individualElementStyles.forEach(item => { - if (item[1] == '100%' || item[1] == '0px' || item[1] == 'inherit') { - // Remove the style or skip - this.renderer.removeStyle(elem, item[0]); - return; - } - this.renderer.setStyle(elem, item[0], item[1], RendererStyleFlags2.Important); - }); - - } - - } - - - toggleDarkMode(force?: boolean) { - if (force !== undefined) { - this.darkMode = force; - } else { - this.darkMode = !this.darkMode; - } - - this.setOverrideStyles(); - } - - toggleReadingDirection() { - if (this.readingDirection === ReadingDirection.LeftToRight) { - this.readingDirection = ReadingDirection.RightToLeft; - } else { - this.readingDirection = ReadingDirection.LeftToRight; - } - } - - getDarkModeBackgroundColor() { - return this.darkMode ? '#292929' : '#fff'; - } - - setOverrideStyles() { - const bodyNode = this.document.querySelector('body'); - if (bodyNode !== undefined && bodyNode !== null) { - if (this.themeService.isDarkTheme()) { - bodyNode.classList.remove('bg-dark'); - } - - bodyNode.style.background = this.getDarkModeBackgroundColor(); - } - this.backgroundColor = this.getDarkModeBackgroundColor(); - const head = this.document.querySelector('head'); - if (this.darkMode) { - this.renderer.appendChild(head, this.darkModeStyleElem) - } else { - this.renderer.removeChild(head, this.darkModeStyleElem); - } - } - - toggleDrawer() { - this.topOffset = this.stickyTopElemRef.nativeElement?.offsetHeight; - this.drawerOpen = !this.drawerOpen; - } - - closeDrawer() { - this.drawerOpen = false; - } - - handleReaderClick(event: MouseEvent) { - if (this.drawerOpen) { - this.closeDrawer(); - event.stopPropagation(); - event.preventDefault(); - } - } - - - scrollTo(partSelector: string) { - if (partSelector.startsWith('#')) { - partSelector = partSelector.substr(1, partSelector.length); - } - - let element = null; - if (partSelector.startsWith('//') || partSelector.startsWith('id(')) { - // Part selector is a XPATH - element = this.getElementFromXPath(partSelector); - } else { - element = this.document.querySelector('*[id="' + partSelector + '"]'); - } - - if (element === null) return; - const fromTopOffset = element.getBoundingClientRect().top + window.pageYOffset + TOP_OFFSET; - // We need to use a delay as webkit browsers (aka apple devices) don't always have the document rendered by this point - setTimeout(() => this.scrollService.scrollTo(fromTopOffset, this.reader.nativeElement), 10); - } - - toggleClickToPaginate() { - this.clickToPaginate = !this.clickToPaginate; - - if (this.clickToPaginateVisualOverlayTimeout2 !== undefined) { - clearTimeout(this.clickToPaginateVisualOverlayTimeout2); - this.clickToPaginateVisualOverlayTimeout2 = undefined; - } - if (!this.clickToPaginate) { return; } + // if (this.clickToPaginateVisualOverlayTimeout2 !== undefined) { + // clearTimeout(this.clickToPaginateVisualOverlayTimeout2); + // this.clickToPaginateVisualOverlayTimeout2 = undefined; + // } + this.clearTimeout(this.clickToPaginateVisualOverlayTimeout2); + if (!clickToPaginate) { return; } this.clickToPaginateVisualOverlayTimeout2 = setTimeout(() => { this.showClickToPaginateVisualOverlay(); }, 200); } + clearTimeout(timeoutId: number | undefined) { + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + timeoutId = undefined; + } + } + showClickToPaginateVisualOverlay() { this.clickToPaginateVisualOverlay = true; @@ -1045,60 +1099,20 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } - getElementFromXPath(path: string) { - const node = this.document.evaluate(path, this.document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; - if (node?.nodeType === Node.ELEMENT_NODE) { - return node as Element; - } - return null; - } - - getXPathTo(element: any): string { - if (element === null) return ''; - if (element.id !== '') { return 'id("' + element.id + '")'; } - if (element === this.document.body) { return element.tagName; } - - - let ix = 0; - const siblings = element.parentNode?.childNodes || []; - for (let sibling of siblings) { - if (sibling === element) { - return this.getXPathTo(element.parentNode) + '/' + element.tagName + '[' + (ix + 1) + ']'; - } - if (sibling.nodeType === 1 && sibling.tagName === element.tagName) { - ix++; - } - - } - return ''; - } - /** - * Turns off Incognito mode. This can only happen once if the user clicks the icon. This will modify URL state + * Responsible for returning the class to show an overlay or not + * @param side + * @returns */ - turnOffIncognito() { - this.incognitoMode = false; - const newRoute = this.readerService.getNextChapterUrl(this.router.url, this.chapterId, this.incognitoMode, this.readingListMode, this.readingListId); - window.history.replaceState({}, '', newRoute); - this.toastr.info('Incognito mode is off. Progress will now start being tracked.'); - this.saveProgress(); - } - - toggleFullscreen() { - this.isFullscreen = this.readerService.checkFullscreenMode(); - if (this.isFullscreen) { - this.readerService.exitFullscreen(() => { - this.isFullscreen = false; - this.renderer.removeStyle(this.reader.nativeElement, 'background'); - }); - } else { - this.readerService.enterFullscreen(this.reader.nativeElement, () => { - this.isFullscreen = true; - // HACK: This is a bug with how browsers change the background color for fullscreen mode - if (!this.darkMode) { - this.renderer.setStyle(this.reader.nativeElement, 'background', 'white'); - } - }); + clickOverlayClass(side: 'right' | 'left') { + // TODO: See if we can use RXjs or a component to manage this + if (!this.clickToPaginateVisualOverlay) { + return ''; } + + if (this.readingDirection === ReadingDirection.LeftToRight) { + return side === 'right' ? 'highlight' : 'highlight-2'; + } + return side === 'right' ? 'highlight-2' : 'highlight'; } } diff --git a/UI/Web/src/app/book-reader/book.service.ts b/UI/Web/src/app/book-reader/book.service.ts index bef4ce73d..10d5b6f7c 100644 --- a/UI/Web/src/app/book-reader/book.service.ts +++ b/UI/Web/src/app/book-reader/book.service.ts @@ -10,6 +10,16 @@ export interface BookPage { html: string; } +export interface FontFamily { + /** + * What the user should see + */ + title: string; + /** + * The actual font face + */ + family: string; +} @Injectable({ providedIn: 'root' @@ -20,8 +30,10 @@ export class BookService { constructor(private http: HttpClient) { } - getFontFamilies() { - return ['default', 'EBGaramond', 'Fira Sans', 'Lato', 'Libre Baskerville', 'Merriweather', 'Nanum Gothic', 'RocknRoll One']; + getFontFamilies(): Array { + return [{title: 'default', family: 'default'}, {title: 'EBGaramond', family: 'EBGaramond'}, {title: 'Fira Sans', family: 'Fira_Sans'}, + {title: 'Lato', family: 'Lato'}, {title: 'Libre Baskerville', family: 'Libre_Baskerville'}, {title: 'Merriweather', family: 'Merriweather'}, + {title: 'Nanum Gothic', family: 'Nanum_Gothic'}, {title: 'RocknRoll One', family: 'RocknRoll_One'}, {title: 'Open Dyslexic', family: 'OpenDyslexic2'}]; } getBookChapters(chapterId: number) { diff --git a/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.html b/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.html new file mode 100644 index 000000000..e6c1c23bd --- /dev/null +++ b/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.html @@ -0,0 +1,133 @@ + +
+ + + +

+ +

+
+ +
+
+
+ + +
+
+
+ + + + + + +
+ +
+ + + 1x + + 2.5x + +
+ +
+ + + + + + +
+ +
+ +
+
+
+ +
+ + + +

+ +

+
+ +
+ + +
+
+ +
Click the edges of the screen to paginate
+
+ + +
+
+
+ + Put reader in fullscreen mode + + + + +
+ +
+ +
+
+ + + + + + + + +
+
+ + +
+
+ + + +

+ +

+
+ +
+ + + +
+
+
+ +
+
\ No newline at end of file diff --git a/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.scss b/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.scss new file mode 100644 index 000000000..d9348c704 --- /dev/null +++ b/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.scss @@ -0,0 +1,9 @@ +.dot { + height: 25px; + width: 25px; + border-radius: 50%; +} + +.active { + border: 1px solid var(--primary-color); +} \ No newline at end of file diff --git a/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.ts b/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.ts new file mode 100644 index 000000000..e3ec33ac4 --- /dev/null +++ b/UI/Web/src/app/book-reader/reader-settings/reader-settings.component.ts @@ -0,0 +1,272 @@ +import { DOCUMENT } from '@angular/common'; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { Subject, take, takeUntil } from 'rxjs'; +import { BookPageLayoutMode } from 'src/app/_models/book-page-layout-mode'; +import { BookTheme } from 'src/app/_models/preferences/book-theme'; +import { ReadingDirection } from 'src/app/_models/preferences/reading-direction'; +import { ThemeProvider } from 'src/app/_models/preferences/site-theme'; +import { User } from 'src/app/_models/user'; +import { AccountService } from 'src/app/_services/account.service'; +import { ThemeService } from 'src/app/_services/theme.service'; +import { BookService, FontFamily } from '../book.service'; +import { BookBlackTheme } from '../_models/book-black-theme'; +import { BookDarkTheme } from '../_models/book-dark-theme'; +import { BookWhiteTheme } from '../_models/book-white-theme'; + +/** + * Used for book reader. Do not use for other components + */ +export interface PageStyle { + 'font-family': string; + 'font-size': string; + 'line-height': string; + 'margin-left': string; + 'margin-right': string; +} + +export const bookColorThemes = [ + { + name: 'Dark', + colorHash: '#292929', + isDarkTheme: true, + isDefault: true, + provider: ThemeProvider.System, + selector: 'brtheme-dark', + content: BookDarkTheme + }, + { + name: 'Black', + colorHash: '#000000', + isDarkTheme: true, + isDefault: false, + provider: ThemeProvider.System, + selector: 'brtheme-black', + content: BookBlackTheme + }, + { + name: 'White', + colorHash: '#FFFFFF', + isDarkTheme: false, + isDefault: false, + provider: ThemeProvider.System, + selector: 'brtheme-white', + content: BookWhiteTheme + }, +]; + +const mobileBreakpointMarginOverride = 700; + +@Component({ + selector: 'app-reader-settings', + templateUrl: './reader-settings.component.html', + styleUrls: ['./reader-settings.component.scss'] +}) +export class ReaderSettingsComponent implements OnInit, OnDestroy { + + /** + * Outputs when clickToPaginate is changed + */ + @Output() clickToPaginateChanged: EventEmitter = new EventEmitter(); + /** + * Outputs when a style is updated and the reader needs to render it + */ + @Output() styleUpdate: EventEmitter = new EventEmitter(); + /** + * Outputs when a theme/dark mode is updated + */ + @Output() colorThemeUpdate: EventEmitter = new EventEmitter(); + /** + * Outputs when a layout mode is updated + */ + @Output() layoutModeUpdate: EventEmitter = new EventEmitter(); + /** + * Outputs when fullscreen is toggled + */ + @Output() fullscreen: EventEmitter = new EventEmitter(); + /** + * Outputs when reading direction is changed + */ + @Output() readingDirection: EventEmitter = new EventEmitter(); + + user!: User; + /** + * List of all font families user can select from + */ + fontOptions: Array = []; + fontFamilies: Array = []; + /** + * Internal property used to capture all the different css properties to render on all elements + */ + pageStyles!: PageStyle; + + readingDirectionModel: ReadingDirection = ReadingDirection.LeftToRight; + + activeTheme: BookTheme | undefined; + + isFullscreen: boolean = false; + + settingsForm: FormGroup = new FormGroup({}); + + /** + * System provided themes + */ + themes: Array = bookColorThemes; + + + private onDestroy: Subject = new Subject(); + + + get BookPageLayoutMode(): typeof BookPageLayoutMode { + return BookPageLayoutMode; + } + + get ReadingDirection() { + return ReadingDirection; + } + + + + constructor(private bookService: BookService, private accountService: AccountService, @Inject(DOCUMENT) private document: Document, private themeService: ThemeService) {} + + ngOnInit(): void { + + this.fontFamilies = this.bookService.getFontFamilies(); + this.fontOptions = this.fontFamilies.map(f => f.title); + + this.accountService.currentUser$.pipe(take(1)).subscribe(user => { + if (user) { + this.user = user; + + if (this.user.preferences.bookReaderFontFamily === undefined) { + this.user.preferences.bookReaderFontFamily = 'default'; + } + if (this.user.preferences.bookReaderFontSize === undefined || this.user.preferences.bookReaderFontSize < 50) { + this.user.preferences.bookReaderFontSize = 100; + } + if (this.user.preferences.bookReaderLineSpacing === undefined || this.user.preferences.bookReaderLineSpacing < 100) { + this.user.preferences.bookReaderLineSpacing = 100; + } + if (this.user.preferences.bookReaderMargin === undefined) { + this.user.preferences.bookReaderMargin = 0; + } + if (this.user.preferences.bookReaderReadingDirection === undefined) { + this.user.preferences.bookReaderReadingDirection = ReadingDirection.LeftToRight; + } + + + this.readingDirectionModel = this.user.preferences.bookReaderReadingDirection; + + this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.bookReaderFontFamily, [])); + this.settingsForm.get('bookReaderFontFamily')!.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(fontName => { + const familyName = this.fontFamilies.filter(f => f.title === fontName)[0].family; + if (familyName === 'default') { + this.pageStyles['font-family'] = 'inherit'; + } else { + this.pageStyles['font-family'] = "'" + familyName + "'"; + } + + this.styleUpdate.emit(this.pageStyles); + }); + + this.settingsForm.addControl('bookReaderFontSize', new FormControl(this.user.preferences.bookReaderFontSize, [])); + this.settingsForm.get('bookReaderFontSize')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(value => { + this.pageStyles['font-size'] = value + '%'; + this.styleUpdate.emit(this.pageStyles); + }); + + this.settingsForm.addControl('bookReaderTapToPaginate', new FormControl(this.user.preferences.bookReaderTapToPaginate, [])); + this.settingsForm.get('bookReaderTapToPaginate')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(value => { + this.clickToPaginateChanged.emit(value); + }); + + + this.settingsForm.addControl('bookReaderLineSpacing', new FormControl(this.user.preferences.bookReaderLineSpacing, [])); + this.settingsForm.get('bookReaderLineSpacing')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(value => { + this.pageStyles['line-height'] = value + '%'; + this.styleUpdate.emit(this.pageStyles); + }); + + this.settingsForm.addControl('bookReaderMargin', new FormControl(this.user.preferences.bookReaderMargin, [])); + this.settingsForm.get('bookReaderMargin')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(value => { + this.pageStyles['margin-left'] = value + '%'; + this.pageStyles['margin-right'] = value + '%'; + this.styleUpdate.emit(this.pageStyles); + }); + + this.settingsForm.addControl('layoutMode', new FormControl(this.user.preferences.bookReaderLayoutMode || BookPageLayoutMode.Default, [])); + this.settingsForm.get('layoutMode')?.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe((layoutMode: BookPageLayoutMode) => { + console.log(layoutMode); + this.layoutModeUpdate.emit(layoutMode); + }); + + this.setTheme(this.user.preferences.bookReaderThemeName || this.themeService.defaultBookTheme); + this.resetSettings(); + } else { + this.resetSettings(); + } + + + }); + } + + ngOnDestroy(): void { + this.onDestroy.next(); + this.onDestroy.complete(); + } + + + resetSettings() { + if (this.user) { + this.setPageStyles(this.user.preferences.bookReaderFontFamily, this.user.preferences.bookReaderFontSize + '%', this.user.preferences.bookReaderMargin + '%', this.user.preferences.bookReaderLineSpacing + '%'); + } else { + this.setPageStyles(); + } + + this.settingsForm.get('bookReaderFontFamily')?.setValue(this.user.preferences.bookReaderFontFamily); + this.styleUpdate.emit(this.pageStyles); + } + + /** + * Internal method to be used by resetSettings. Pass items in with quantifiers + */ + setPageStyles(fontFamily?: string, fontSize?: string, margin?: string, lineHeight?: string, colorTheme?: string) { + const windowWidth = window.innerWidth + || this.document.documentElement.clientWidth + || this.document.body.clientWidth; + + + let defaultMargin = '15%'; + if (windowWidth <= mobileBreakpointMarginOverride) { + defaultMargin = '5%'; + } + this.pageStyles = { + 'font-family': fontFamily || this.pageStyles['font-family'] || 'default', + 'font-size': fontSize || this.pageStyles['font-size'] || '100%', + 'margin-left': margin || this.pageStyles['margin-left'] || defaultMargin, + 'margin-right': margin || this.pageStyles['margin-right'] || defaultMargin, + 'line-height': lineHeight || this.pageStyles['line-height'] || '100%' + }; + + } + + setTheme(themeName: string) { + const theme = this.themes.find(t => t.name === themeName); + this.activeTheme = theme; + this.colorThemeUpdate.emit(theme); + } + + toggleReadingDirection() { + if (this.readingDirectionModel === ReadingDirection.LeftToRight) { + this.readingDirectionModel = ReadingDirection.RightToLeft; + } else { + this.readingDirectionModel = ReadingDirection.LeftToRight; + } + + this.readingDirection.emit(this.readingDirectionModel); + } + + toggleFullscreen() { + this.fullscreen.emit(); + } +} diff --git a/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.html b/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.html new file mode 100644 index 000000000..174e8cf8c --- /dev/null +++ b/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.html @@ -0,0 +1,25 @@ +
+

Table of Contents

+
+ This book does not have Table of Contents set in the metadata or a toc file +
+
+ +
+ + + +
\ No newline at end of file diff --git a/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.scss b/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.scss new file mode 100644 index 000000000..6ae729a59 --- /dev/null +++ b/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.scss @@ -0,0 +1,11 @@ +.table-of-contents li { + cursor: pointer; + + &.active { + font-weight: bold; + } +} + +.chapter-title { + padding-inline-start: 0px +} \ No newline at end of file diff --git a/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.ts b/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.ts new file mode 100644 index 000000000..101a09191 --- /dev/null +++ b/UI/Web/src/app/book-reader/table-of-contents/table-of-contents.component.ts @@ -0,0 +1,48 @@ +import { AfterViewInit, Component, ElementRef, EventEmitter, Inject, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; +import { Subject } from 'rxjs'; +import { BookChapterItem } from '../_models/book-chapter-item'; + +@Component({ + selector: 'app-table-of-contents', + templateUrl: './table-of-contents.component.html', + styleUrls: ['./table-of-contents.component.scss'] +}) +export class TableOfContentsComponent implements OnInit, OnDestroy { + + @Input() chapterId!: number; + @Input() pageNum!: number; + @Input() currentPageAnchor!: string; + @Input() chapters:Array = []; + + @Output() loadChapter: EventEmitter<{pageNum: number, part: string}> = new EventEmitter(); + + + + private onDestroy: Subject = new Subject(); + + + pageAnchors: {[n: string]: number } = {}; + + constructor() {} + + ngOnInit(): void { + } + + ngOnDestroy(): void { + this.onDestroy.next(); + this.onDestroy.complete(); + } + + cleanIdSelector(id: string) { + const tokens = id.split('/'); + if (tokens.length > 0) { + return tokens[0]; + } + return id; + } + + loadChapterPage(pageNum: number, part: string) { + this.loadChapter.emit({pageNum, part}); + } + +} diff --git a/UI/Web/src/app/dev-only/theme-test/theme-test.component.ts b/UI/Web/src/app/dev-only/theme-test/theme-test.component.ts index 7bd6635f8..be3ab498b 100644 --- a/UI/Web/src/app/dev-only/theme-test/theme-test.component.ts +++ b/UI/Web/src/app/dev-only/theme-test/theme-test.component.ts @@ -40,12 +40,12 @@ export class ThemeTestComponent implements OnInit { latestReadDate: '', localizedName: '', originalName: '', - sortName: '', + sortName: '', userRating: 0, - userReview: '', + userReview: '', volumes: [], localizedNameLocked: false, - nameLocked: false, + nameLocked: false, sortNameLocked: false, lastChapterAdded: '', } @@ -62,12 +62,12 @@ export class ThemeTestComponent implements OnInit { latestReadDate: '', localizedName: '', originalName: '', - sortName: '', + sortName: '', userRating: 0, - userReview: '', + userReview: '', volumes: [], localizedNameLocked: false, - nameLocked: false, + nameLocked: false, sortNameLocked: false, lastChapterAdded: '', } diff --git a/UI/Web/src/app/metadata-filter/metadata-filter.component.html b/UI/Web/src/app/metadata-filter/metadata-filter.component.html index 943858007..340524a60 100644 --- a/UI/Web/src/app/metadata-filter/metadata-filter.component.html +++ b/UI/Web/src/app/metadata-filter/metadata-filter.component.html @@ -1,21 +1,21 @@ -
-
- + +
+
+ +
-
-
- -
-

Book Settings - -

-
-
- -
-
-
+
+ +
+ Filter +
+
+ +
+
+
+ This is library agnostic diff --git a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts index 212966835..08e9c71c6 100644 --- a/UI/Web/src/app/metadata-filter/metadata-filter.component.ts +++ b/UI/Web/src/app/metadata-filter/metadata-filter.component.ts @@ -2,7 +2,7 @@ import { Component, ContentChild, EventEmitter, Input, OnDestroy, OnInit, Output import { FormControl, FormGroup } from '@angular/forms'; import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap'; import { distinctUntilChanged, forkJoin, map, Observable, of, ReplaySubject, Subject, takeUntil } from 'rxjs'; -import { Breakpoint, UtilityService } from '../shared/_services/utility.service'; +import { UtilityService } from '../shared/_services/utility.service'; import { TypeaheadSettings } from '../typeahead/typeahead-settings'; import { CollectionTag } from '../_models/collection-tag'; import { Genre } from '../_models/genre'; @@ -17,8 +17,8 @@ import { Tag } from '../_models/tag'; import { CollectionTagService } from '../_services/collection-tag.service'; import { LibraryService } from '../_services/library.service'; import { MetadataService } from '../_services/metadata.service'; -import { NavService } from '../_services/nav.service'; import { SeriesService } from '../_services/series.service'; +import { ToggleService } from '../_services/toggle.service'; import { FilterSettings } from './filter-settings'; @Component({ @@ -43,7 +43,6 @@ export class MetadataFilterComponent implements OnInit, OnDestroy { @Output() applyFilter: EventEmitter = new EventEmitter(); @ContentChild('[ngbCollapse]') collapse!: NgbCollapse; - //@ContentChild('commentDrawer') commentDrawer: formatSettings: TypeaheadSettings> = new TypeaheadSettings(); @@ -87,7 +86,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy { } constructor(private libraryService: LibraryService, private metadataService: MetadataService, private seriesService: SeriesService, - private utilityService: UtilityService, private collectionTagService: CollectionTagService) { + private utilityService: UtilityService, private collectionTagService: CollectionTagService, public toggleService: ToggleService) { } ngOnInit(): void { @@ -98,6 +97,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy { if (this.filterOpen) { this.filterOpen.pipe(takeUntil(this.onDestroy)).subscribe(openState => { this.filteringCollapsed = !openState; + this.toggleService.set(!this.filteringCollapsed); }); } @@ -161,6 +161,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy { close() { this.filterOpen.emit(false); this.filteringCollapsed = true; + this.toggleService.set(!this.filteringCollapsed); } ngOnDestroy() { @@ -213,6 +214,7 @@ export class MetadataFilterComponent implements OnInit, OnDestroy { this.resetTypeaheads.next(false); // Pass false to ensure we reset to the preset and not to an empty typeahead if (this.filterSettings.openByDefault) { this.filteringCollapsed = false; + this.toggleService.set(!this.filteringCollapsed); } this.apply(); }); @@ -598,4 +600,14 @@ export class MetadataFilterComponent implements OnInit, OnDestroy { this.updateApplied++; } + toggleSelected() { + //this.filteringCollapsed = !this.filteringCollapsed; + this.toggleService.toggle(); + } + + setToggle(event: any) { + console.log('set toggle', event); + this.toggleService.set(!this.filteringCollapsed); + } + } diff --git a/UI/Web/src/app/registration/reset-password/reset-password.component.scss b/UI/Web/src/app/registration/reset-password/reset-password.component.scss index 9aedb3438..736856043 100644 --- a/UI/Web/src/app/registration/reset-password/reset-password.component.scss +++ b/UI/Web/src/app/registration/reset-password/reset-password.component.scss @@ -4,5 +4,5 @@ .custom-input { background-color: #fff !important; - color: black; + color: black !important; } \ No newline at end of file diff --git a/UI/Web/src/app/registration/user-login/user-login.component.scss b/UI/Web/src/app/registration/user-login/user-login.component.scss index fb13d8579..e37d91cff 100644 --- a/UI/Web/src/app/registration/user-login/user-login.component.scss +++ b/UI/Web/src/app/registration/user-login/user-login.component.scss @@ -12,7 +12,7 @@ a { .custom-input { background-color: #fff !important; - color: black; + color: black !important; } .invalid-feedback { diff --git a/UI/Web/src/app/shared/drawer/drawer.component.html b/UI/Web/src/app/shared/drawer/drawer.component.html index 722ba4e5f..4d7c21972 100644 --- a/UI/Web/src/app/shared/drawer/drawer.component.html +++ b/UI/Web/src/app/shared/drawer/drawer.component.html @@ -1,15 +1,18 @@ -
-
- + + +
diff --git a/UI/Web/src/app/shared/drawer/drawer.component.scss b/UI/Web/src/app/shared/drawer/drawer.component.scss index b8b909772..809ff86d1 100644 --- a/UI/Web/src/app/shared/drawer/drawer.component.scss +++ b/UI/Web/src/app/shared/drawer/drawer.component.scss @@ -1,56 +1,8 @@ -:host { - --drawer-height: 100vh; - --drawer-width: 400px; - --drawer-top-offset: 0px; - //--drawer-background-color: #fff; - } - - .drawer-container { - position: absolute; - top: var(--drawer-top-offset); - right: 0; - width: var(--drawer-width); - height: 100vh; - background: var(--drawer-background-color, #fff); - transition: all 300ms; - box-shadow: 0 6px 4px 2px rgb(0 0 0 / 70%); - padding: 10px 10px; - z-index: 1021; - overflow: auto; - -webkit-overflow-scrolling: touch; +.offcanvas { + color: var(--drawer-text-color); + background-color: var(--drawer-bg-color); +} - &.position-right { - right: calc(-1 * var(--drawer-width)); - &.is-open { - right: 0; - } - } - &.position-left { - left: calc(-1 * var(--drawer-width)); - &.is-open { - left: 0; - } - } - - &.position-bottom { - //top: calc(-1 * var(--drawer-height)); - top: 100vh; - height: 0px; - &.is-open { - //bottom: 0; - top: 100vh; - height: var(--drawer-height); - } - } - - &.position-top { - //bottom: calc(-1 * var(--drawer-height)); - top: 0px; - height: 0px; - &.is-open { - //bottom: 0; - top: 0px; - height: var(--drawer-height); - } - } - } +.hide-if-empty:empty { + display: none !important; +} \ No newline at end of file diff --git a/UI/Web/src/app/shared/drawer/drawer.component.ts b/UI/Web/src/app/shared/drawer/drawer.component.ts index f83e95383..3af6a5db0 100644 --- a/UI/Web/src/app/shared/drawer/drawer.component.ts +++ b/UI/Web/src/app/shared/drawer/drawer.component.ts @@ -14,18 +14,20 @@ export class DrawerOptions { exportAs: "drawer" }) export class DrawerComponent { - @Input() isOpen = false; @Input() width: number = 400; /** * Side of the screen the drawer should animate from */ - @Input() position: 'left' | 'right' | 'bottom' = 'left'; + @Input() position: 'start' | 'end' | 'bottom' | 'top' = 'start'; @Input() options: Partial = new DrawerOptions(); @Output() drawerClosed = new EventEmitter(); + @Output() isOpenChange: EventEmitter = new EventEmitter(); close() { - this.drawerClosed.emit(); + this.isOpen = false; + this.isOpenChange.emit(false); + this.drawerClosed.emit(false); } } diff --git a/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.html b/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.html index fe4bbfa3f..f1d36226f 100644 --- a/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.html +++ b/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.html @@ -7,7 +7,8 @@
- diff --git a/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.ts b/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.ts index 6ded316b4..9d7fa8825 100644 --- a/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.ts +++ b/UI/Web/src/app/sidenav/side-nav-companion-bar/side-nav-companion-bar.component.ts @@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angu import { Subject, takeUntil } from 'rxjs'; import { Breakpoint, UtilityService } from 'src/app/shared/_services/utility.service'; import { NavService } from 'src/app/_services/nav.service'; +import { ToggleService } from 'src/app/_services/toggle.service'; /** * This should go on all pages which have the side nav present and is not Settings related. @@ -39,7 +40,7 @@ export class SideNavCompanionBarComponent implements OnInit, OnDestroy { private onDestroy: Subject = new Subject(); - constructor(private navService: NavService, private utilityService: UtilityService) { + constructor(private navService: NavService, private utilityService: UtilityService, public toggleService: ToggleService) { } ngOnInit(): void { diff --git a/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.html b/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.html index 3f5e23059..fdaf7a3fb 100644 --- a/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.html +++ b/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.html @@ -9,6 +9,7 @@
+

Site Themes

@@ -20,6 +21,5 @@
-
diff --git a/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.ts b/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.ts index f0a544815..9a2381e89 100644 --- a/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.ts +++ b/UI/Web/src/app/user-settings/theme-manager/theme-manager.component.ts @@ -2,6 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { ToastrService } from 'ngx-toastr'; import { distinctUntilChanged, Subject, take, takeUntil } from 'rxjs'; import { ThemeService } from 'src/app/_services/theme.service'; +import { BookTheme } from 'src/app/_models/preferences/book-theme'; import { SiteTheme, ThemeProvider } from 'src/app/_models/preferences/site-theme'; import { User } from 'src/app/_models/user'; import { AccountService } from 'src/app/_services/account.service'; @@ -45,7 +46,7 @@ export class ThemeManagerComponent implements OnInit, OnDestroy { } applyTheme(theme: SiteTheme) { - + if (this.user) { const pref = Object.assign({}, this.user.preferences); pref.theme = theme; @@ -56,7 +57,7 @@ export class ThemeManagerComponent implements OnInit, OnDestroy { this.themeService.setTheme(theme.name); }); } - + } updateDefault(theme: SiteTheme) { diff --git a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html index cf99950c1..db26de166 100644 --- a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html +++ b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.html @@ -13,97 +13,87 @@ These are global settings that are bound to your account.

- - - - -

- -

-
- -
-

Image Reader

+ -
-
-   - Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page. - Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page. - + + + +

+ +

+
+ +
+
+   + Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page. + Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page. + +
+ +
+   + How to scale the image to your screen. + How to scale the image to your screen. + +
-
-   - How to scale the image to your screen. - How to scale the image to your screen. - +
+
+   + How to split a full width image (ie both left and right images are combined) + How to split a full width image (ie both left and right images are combined) + +
+
+ + +
-
-
-
-   - How to split a full width image (ie both left and right images are combined) - How to split a full width image (ie both left and right images are combined) - +
+
+   + Render a single image to the screen to two side-by-side images + + +
+
+ + +
-
- - -
-
- -
-
-   - Render a single image to the screen to two side-by-side images - - -
-
- - -
-
-
-
-
- -
+
+
+
-
-
-
- -
+
+
@@ -111,28 +101,32 @@
-
-
-

Book Reader

-
-
- -
-
- - -
-
-
-
+
+ + +
+ + + + + +

+ +

+
+ +
+
  - Should the sides of the book reader screen allow tapping on it to move to prev/next page - Should the sides of the book reader screen allow tapping on it to move to prev/next page + Should the sides of the book reader screen allow tapping on it to move to prev/next page + Should the sides of the book reader screen allow tapping on it to move to prev/next page
@@ -140,7 +134,7 @@
-   +   Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page. Direction to click to move to next page. Right to Left means you click on left side of screen to move to next page. + +
-
-   - How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting. - How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting. -
+ +
+   + What color theme to apply to the book reader content and menuing + +
+
+
+ + + {{settingsForm.get('bookReaderFontSize')?.value + '%'}} +
+ +
+
+   + How much spacing between the lines of the book + How much spacing between the lines of the book +
+ + {{settingsForm.get('bookReaderLineSpacing')?.value + '%'}} +
+ +
+
+   + How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting. + How much spacing on each side of the screen. This will override to 0 on mobile devices regardless of this setting. +
+ + + {{settingsForm.get('bookReaderMargin')?.value + '%'}} +
+
- - - - + + + + diff --git a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.scss b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.scss index e86d1bd19..84f37233b 100644 --- a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.scss +++ b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.scss @@ -4,4 +4,15 @@ .container { padding-top: 10px; +} + +.form-range { + width: 90%; +} +.range-label { + width: 100%; +} +.range-text { + vertical-align: top; + margin-left: 5px; } \ No newline at end of file diff --git a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.ts b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.ts index 07ff48b7b..57f351612 100644 --- a/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.ts +++ b/UI/Web/src/app/user-settings/user-preferences/user-preferences.component.ts @@ -2,17 +2,22 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { ToastrService } from 'ngx-toastr'; import { take } from 'rxjs/operators'; -import { Options } from '@angular-slider/ngx-slider'; import { Title } from '@angular/platform-browser'; import { BookService } from 'src/app/book-reader/book.service'; -import { readingDirections, scalingOptions, pageSplitOptions, readingModes, Preferences, layoutModes } from 'src/app/_models/preferences/preferences'; +import { readingDirections, scalingOptions, pageSplitOptions, readingModes, Preferences, bookLayoutModes, layoutModes } from 'src/app/_models/preferences/preferences'; import { User } from 'src/app/_models/user'; import { AccountService } from 'src/app/_services/account.service'; -import { NavService } from 'src/app/_services/nav.service'; import { ActivatedRoute, Router } from '@angular/router'; import { SettingsService } from 'src/app/admin/settings.service'; +import { bookColorThemes } from 'src/app/book-reader/reader-settings/reader-settings.component'; +import { BookPageLayoutMode } from 'src/app/_models/book-page-layout-mode'; import { forkJoin } from 'rxjs'; +enum AccordionPanelID { + ImageReader = 'image-reader', + BookReader = 'book-reader' +} + @Component({ selector: 'app-user-preferences', templateUrl: './user-preferences.component.html', @@ -25,6 +30,8 @@ export class UserPreferencesComponent implements OnInit, OnDestroy { pageSplitOptions = pageSplitOptions; readingModes = readingModes; layoutModes = layoutModes; + bookLayoutModes = bookLayoutModes; + bookColorThemes = bookColorThemes; settingsForm: FormGroup = new FormGroup({}); passwordChangeForm: FormGroup = new FormGroup({}); @@ -36,22 +43,6 @@ export class UserPreferencesComponent implements OnInit, OnDestroy { resetPasswordErrors: string[] = []; obserableHandles: Array = []; - - bookReaderLineSpacingOptions: Options = { - floor: 100, - ceil: 250, - step: 10, - }; - bookReaderMarginOptions: Options = { - floor: 0, - ceil: 30, - step: 5, - }; - bookReaderFontSizeOptions: Options = { - floor: 50, - ceil: 300, - step: 10, - }; fontFamilies: Array = []; tabs: Array<{title: string, fragment: string}> = [ @@ -64,12 +55,14 @@ export class UserPreferencesComponent implements OnInit, OnDestroy { opdsEnabled: boolean = false; makeUrl: (val: string) => string = (val: string) => {return this.transformKeyToOpdsUrl(val)}; - backgroundColor: any; // TODO: Hook into user pref + get AccordionPanelID() { + return AccordionPanelID; + } - constructor(private accountService: AccountService, private toastr: ToastrService, private bookService: BookService, - private navService: NavService, private titleService: Title, private route: ActivatedRoute, private settingsService: SettingsService, + constructor(private accountService: AccountService, private toastr: ToastrService, private bookService: BookService, + private titleService: Title, private route: ActivatedRoute, private settingsService: SettingsService, private router: Router) { - this.fontFamilies = this.bookService.getFontFamilies(); + this.fontFamilies = this.bookService.getFontFamilies().map(f => f.title); this.route.fragment.subscribe(frag => { const tab = this.tabs.filter(item => item.fragment === frag); @@ -105,7 +98,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy { if (this.fontFamilies.indexOf(this.user.preferences.bookReaderFontFamily) < 0) { this.user.preferences.bookReaderFontFamily = 'default'; } - + this.settingsForm.addControl('readingDirection', new FormControl(this.user.preferences.readingDirection, [])); this.settingsForm.addControl('scalingOption', new FormControl(this.user.preferences.scalingOption, [])); this.settingsForm.addControl('pageSplitOption', new FormControl(this.user.preferences.pageSplitOption, [])); @@ -113,13 +106,14 @@ export class UserPreferencesComponent implements OnInit, OnDestroy { this.settingsForm.addControl('showScreenHints', new FormControl(this.user.preferences.showScreenHints, [])); this.settingsForm.addControl('readerMode', new FormControl(this.user.preferences.readerMode, [])); this.settingsForm.addControl('layoutMode', new FormControl(this.user.preferences.layoutMode, [])); - this.settingsForm.addControl('bookReaderDarkMode', new FormControl(this.user.preferences.bookReaderDarkMode, [])); this.settingsForm.addControl('bookReaderFontFamily', new FormControl(this.user.preferences.bookReaderFontFamily, [])); this.settingsForm.addControl('bookReaderFontSize', new FormControl(this.user.preferences.bookReaderFontSize, [])); this.settingsForm.addControl('bookReaderLineSpacing', new FormControl(this.user.preferences.bookReaderLineSpacing, [])); this.settingsForm.addControl('bookReaderMargin', new FormControl(this.user.preferences.bookReaderMargin, [])); this.settingsForm.addControl('bookReaderReadingDirection', new FormControl(this.user.preferences.bookReaderReadingDirection, [])); this.settingsForm.addControl('bookReaderTapToPaginate', new FormControl(!!this.user.preferences.bookReaderTapToPaginate, [])); + this.settingsForm.addControl('bookReaderLayoutMode', new FormControl(this.user.preferences.bookReaderLayoutMode || BookPageLayoutMode.Default, [])); + this.settingsForm.addControl('bookReaderThemeName', new FormControl(this.user?.preferences.bookReaderThemeName || bookColorThemes[0].name, [])); this.settingsForm.addControl('theme', new FormControl(this.user.preferences.theme, [])); }); @@ -149,13 +143,14 @@ export class UserPreferencesComponent implements OnInit, OnDestroy { this.settingsForm.get('readerMode')?.setValue(this.user.preferences.readerMode); this.settingsForm.get('layoutMode')?.setValue(this.user.preferences.layoutMode); this.settingsForm.get('pageSplitOption')?.setValue(this.user.preferences.pageSplitOption); - this.settingsForm.get('bookReaderDarkMode')?.setValue(this.user.preferences.bookReaderDarkMode); this.settingsForm.get('bookReaderFontFamily')?.setValue(this.user.preferences.bookReaderFontFamily); this.settingsForm.get('bookReaderFontSize')?.setValue(this.user.preferences.bookReaderFontSize); this.settingsForm.get('bookReaderLineSpacing')?.setValue(this.user.preferences.bookReaderLineSpacing); this.settingsForm.get('bookReaderMargin')?.setValue(this.user.preferences.bookReaderMargin); this.settingsForm.get('bookReaderTapToPaginate')?.setValue(this.user.preferences.bookReaderTapToPaginate); this.settingsForm.get('bookReaderReadingDirection')?.setValue(this.user.preferences.bookReaderReadingDirection); + this.settingsForm.get('bookReaderLayoutMode')?.setValue(this.user.preferences.bookReaderLayoutMode); + this.settingsForm.get('bookReaderThemeName')?.setValue(this.user.preferences.bookReaderThemeName); this.settingsForm.get('theme')?.setValue(this.user.preferences.theme); } @@ -169,23 +164,25 @@ export class UserPreferencesComponent implements OnInit, OnDestroy { if (this.user === undefined) return; const modelSettings = this.settingsForm.value; const data: Preferences = { - readingDirection: parseInt(modelSettings.readingDirection, 10), - scalingOption: parseInt(modelSettings.scalingOption, 10), - pageSplitOption: parseInt(modelSettings.pageSplitOption, 10), - autoCloseMenu: modelSettings.autoCloseMenu, - readerMode: parseInt(modelSettings.readerMode, 10), + readingDirection: parseInt(modelSettings.readingDirection, 10), + scalingOption: parseInt(modelSettings.scalingOption, 10), + pageSplitOption: parseInt(modelSettings.pageSplitOption, 10), + autoCloseMenu: modelSettings.autoCloseMenu, + readerMode: parseInt(modelSettings.readerMode, 10), layoutMode: parseInt(modelSettings.layoutMode, 10), showScreenHints: modelSettings.showScreenHints, - backgroundColor: this.user.preferences.backgroundColor, - bookReaderDarkMode: modelSettings.bookReaderDarkMode, + backgroundColor: modelSettings.backgroundColor, // this.user.preferences.backgroundColor, bookReaderFontFamily: modelSettings.bookReaderFontFamily, bookReaderLineSpacing: modelSettings.bookReaderLineSpacing, bookReaderFontSize: modelSettings.bookReaderFontSize, bookReaderMargin: modelSettings.bookReaderMargin, bookReaderTapToPaginate: modelSettings.bookReaderTapToPaginate, bookReaderReadingDirection: parseInt(modelSettings.bookReaderReadingDirection, 10), + bookReaderLayoutMode: parseInt(modelSettings.bookReaderLayoutMode, 10), + bookReaderThemeName: modelSettings.bookReaderThemeName, theme: modelSettings.theme }; + this.obserableHandles.push(this.accountService.updatePreferences(data).subscribe((updatedPrefs) => { this.toastr.success('Server settings updated'); if (this.user) { diff --git a/UI/Web/src/app/user-settings/user-settings.module.ts b/UI/Web/src/app/user-settings/user-settings.module.ts index 67e8e6ec3..856924289 100644 --- a/UI/Web/src/app/user-settings/user-settings.module.ts +++ b/UI/Web/src/app/user-settings/user-settings.module.ts @@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common'; import { UserPreferencesComponent } from './user-preferences/user-preferences.component'; import { NgbAccordionModule, NgbNavModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; import { ReactiveFormsModule } from '@angular/forms'; -import { NgxSliderModule } from '@angular-slider/ngx-slider'; import { UserSettingsRoutingModule } from './user-settings-routing.module'; import { ApiKeyComponent } from './api-key/api-key.component'; import { PipeModule } from '../pipe/pipe.module'; @@ -29,7 +28,6 @@ import { SidenavModule } from '../sidenav/sidenav.module'; NgbNavModule, NgbTooltipModule, - NgxSliderModule, ColorPickerModule, // User prefernces background color PipeModule, diff --git a/UI/Web/src/assets/fonts/OpenDyslexic2/OpenDyslexic-Bold.otf b/UI/Web/src/assets/fonts/OpenDyslexic2/OpenDyslexic-Bold.otf new file mode 100644 index 0000000000000000000000000000000000000000..4c492e2fcc283b05ef4d25ddfb288ea9b47e7a8a GIT binary patch literal 42408 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

GB8A9Plq#we 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

*8VcmWfE3w!eJKmRPt0 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%0D@dD4>=C);k_D+-ZuTns=7R6LnUmC&lC_44&e6 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 z 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