From 78ffb8a8a203ed9012f3a9088d9f295547b32e06 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sat, 2 Apr 2022 09:38:14 -0500 Subject: [PATCH] Release Shakeout (#1186) * Cleaned up some styles on the progress bar in book reader * Fixed up some phone-hidden classes and added titles around the codebase. Stat reporting on first run now takes into account that admin user wont exist. * Fixed manage library page not updating last scan time when a notification event comes in. * Integrated SeriesSort ComicInfo tag (somehow it got missed) * Some minor style changes and no results found for bookmarks on chapter detail modal * Fixed the labels in action bar on book reader so Prev/Next are in same place * Cleaned up some responsive styles around images and reduced custom classes in light of new display classes on collection detail and series detail pages * Fixed an issue with webkit browsers and book reader where the scroll to would fail as the document wasn't fully rendered. A 10ms delay seems to fix the issue. * Cleaned up some code and filtering for collections. Collection detail is missing filtering functionality somehow, disabled the button and will add in future release * Correctly validate and show a message when a user is not an admin or has change password role when going through forget password flow. * Fixed a bug on manage libraries where library last scan didn't work on first scan of a library, due to there being no updated series. * Fixed a rendering issue with text being focused on confirm email page textboxes. Fixed a bug where when deleting a theme that was default, Kavita didn't reset Dark as the default theme. * Cleaned up the naming and styles for side nav active item hover * Fixed event widget to have correct styling on eink and light * Tried to fix a rendering issue on side nav for light themes, but can't figure it out * On light more, ensure switches are green * Fixed a bug where opening a page with a preselected filter, the filter toggle button would require 2 clicks to collapse * Reverted the revert of On Deck. * Improved the upload by url experience by sending a custom fail error to UI when a url returns 401. * When deleting a library, emit a series removed event for each series removed so user's dashboards/screens update. * Fixed an api throwing an error due to text being sent back instead of json. * Fixed a refresh bug with refreshing pending invites after deleting an invite. Ensure we always refresh pending invites even if user cancel's from invite, as they might invite, then hit cancel, where invite is still active. * Fixed a bug where invited users with + in the email would fail due to validation, but UI wouldn't properly inform user. --- API/Controllers/AccountController.cs | 6 +++ API/Controllers/LibraryController.cs | 6 +++ API/Controllers/OPDSController.cs | 18 +++++-- API/Controllers/UploadController.cs | 20 ++++++-- API/Data/Metadata/ComicInfo.cs | 1 + API/Data/Repositories/LibraryRepository.cs | 1 + API/Data/Repositories/SeriesRepository.cs | 2 +- API/Extensions/IdentityServiceExtensions.cs | 6 +++ API/Program.cs | 5 +- API/Services/SeriesService.cs | 2 +- API/Services/TaskScheduler.cs | 4 +- .../Tasks/Scanner/ParseScannedFiles.cs | 7 ++- API/Services/Tasks/ScannerService.cs | 6 +-- API/Services/Tasks/SiteThemeService.cs | 15 ++++++ API/Services/Tasks/StatsService.cs | 18 ++++--- API/SignalR/MessageFactory.cs | 4 +- API/Startup.cs | 3 +- .../review-series-modal.component.html | 2 +- .../app/_models/events/scan-series-event.ts | 1 + .../src/app/_services/reading-list.service.ts | 2 +- UI/Web/src/app/_services/series.service.ts | 1 - .../manage-library.component.html | 6 +-- .../manage-library.component.ts | 50 +++++++++++-------- .../manage-users/manage-users.component.ts | 5 +- .../app/all-series/all-series.component.html | 2 +- .../book-reader/book-reader.component.html | 26 +++++----- .../book-reader/book-reader.component.ts | 5 +- .../card-details-modal.component.html | 3 ++ .../chapter-metadata-detail.component.html | 20 ++++---- .../collection-detail.component.html | 13 ++--- .../collection-detail.component.ts | 31 +++++++++--- .../events-widget.component.scss | 10 ++-- .../library-detail.component.html | 2 +- .../recently-added.component.html | 2 +- .../confirm-email.component.html | 2 +- .../confirm-email.component.scss | 2 +- .../confirm-email/confirm-email.component.ts | 6 ++- .../register/register.component.scss | 4 +- .../register/register.component.ts | 5 +- .../reset-password.component.ts | 2 + .../series-detail.component.html | 10 ++-- .../series-detail.component.scss | 19 +------ .../side-nav-companion-bar.component.ts | 11 ++-- .../side-nav-item.component.scss | 18 +++++-- .../sidenav/side-nav/side-nav.component.ts | 5 +- .../src/app/typeahead/typeahead.component.ts | 4 -- .../app/user-login/user-login.component.html | 2 +- UI/Web/src/theme/themes/dark.scss | 11 +++- UI/Web/src/theme/themes/e-ink.scss | 19 ++++++- UI/Web/src/theme/themes/light.scss | 30 +++++++++-- 50 files changed, 297 insertions(+), 158 deletions(-) diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 7fc8769e7..dfa30b18d 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -525,6 +525,12 @@ namespace API.Controllers return Ok("An email will be sent to the email if it exists in our database"); } + var roles = await _userManager.GetRolesAsync(user); + + + if (!roles.Any(r => r is PolicyConstants.AdminRole or PolicyConstants.ChangePasswordRole)) + return Unauthorized("You are not permitted to this operation."); + var emailLink = GenerateEmailLink(await _userManager.GeneratePasswordResetTokenAsync(user), "confirm-reset-password", user.Email); _logger.LogCritical("[Forgot Password]: Email Link for {UserName}: {Link}", user.UserName, emailLink); var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString(); diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index a5c2ae676..85880a38d 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -197,6 +197,12 @@ namespace API.Controllers _taskScheduler.CleanupChapters(chapterIds); } + foreach (var seriesId in seriesIds) + { + await _eventHub.SendMessageAsync(MessageFactory.SeriesRemoved, + MessageFactory.SeriesRemovedEvent(seriesId, string.Empty, libraryId), false); + } + await _eventHub.SendMessageAsync(MessageFactory.LibraryModified, MessageFactory.LibraryModifiedEvent(libraryId, "delete"), false); return Ok(true); diff --git a/API/Controllers/OPDSController.cs b/API/Controllers/OPDSController.cs index 1e6d381a1..91607db7d 100644 --- a/API/Controllers/OPDSController.cs +++ b/API/Controllers/OPDSController.cs @@ -618,6 +618,12 @@ public class OpdsController : BaseApiController { if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds) return BadRequest("OPDS is not enabled on this server"); + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(await GetUser(apiKey)); + if (!await _downloadService.HasDownloadPermission(user)) + { + return BadRequest("User does not have download permissions"); + } + var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId); var (bytes, contentType, fileDownloadName) = await _downloadService.GetFirstFileDownload(files); return File(bytes, contentType, fileDownloadName); @@ -776,6 +782,7 @@ public class OpdsController : BaseApiController { CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"/api/image/chapter-cover?chapterId={chapterId}"), CreateLink(FeedLinkRelation.Thumbnail, FeedLinkType.Image, $"/api/image/chapter-cover?chapterId={chapterId}"), + accLink, CreatePageStreamLink(seriesId, volumeId, chapterId, mangaFile, apiKey) }, Content = new FeedEntryContent() @@ -785,11 +792,12 @@ public class OpdsController : BaseApiController } }; - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(await GetUser(apiKey)); - if (await _downloadService.HasDownloadPermission(user)) - { - entry.Links.Add(accLink); - } + // We can't not show acc link in the feed, panels wont work like that. We have to block download directly + // var user = await _unitOfWork.UserRepository.GetUserByIdAsync(await GetUser(apiKey)); + // if (await _downloadService.HasDownloadPermission(user)) + // { + // entry.Links.Add(accLink); + // } return entry; diff --git a/API/Controllers/UploadController.cs b/API/Controllers/UploadController.cs index 3dd23db1a..c0cc46e3e 100644 --- a/API/Controllers/UploadController.cs +++ b/API/Controllers/UploadController.cs @@ -47,12 +47,24 @@ namespace API.Controllers { var dateString = $"{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToLongTimeString()}".Replace("/", "_").Replace(":", "_"); var format = _directoryService.FileSystem.Path.GetExtension(dto.Url.Split('?')[0]).Replace(".", ""); - var path = await dto.Url - .DownloadFileAsync(_directoryService.TempDirectory, $"coverupload_{dateString}.{format}"); + try + { + var path = await dto.Url + .DownloadFileAsync(_directoryService.TempDirectory, $"coverupload_{dateString}.{format}"); - if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"Could not download file"); + if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) + return BadRequest($"Could not download file"); - return $"coverupload_{dateString}.{format}"; + return $"coverupload_{dateString}.{format}"; + } + catch (FlurlHttpException ex) + { + // Unauthorized + if (ex.StatusCode == 401) + return BadRequest("The server requires authentication to load the url externally"); + } + + return BadRequest("Unable to download image, please use another url or upload by file"); } /// diff --git a/API/Data/Metadata/ComicInfo.cs b/API/Data/Metadata/ComicInfo.cs index f2f3734a4..040d7f6b7 100644 --- a/API/Data/Metadata/ComicInfo.cs +++ b/API/Data/Metadata/ComicInfo.cs @@ -14,6 +14,7 @@ namespace API.Data.Metadata public string Summary { get; set; } = string.Empty; public string Title { get; set; } = string.Empty; public string Series { get; set; } = string.Empty; + public string SeriesSort { get; set; } = string.Empty; public string Number { get; set; } = string.Empty; /// /// The total number of items in the series. diff --git a/API/Data/Repositories/LibraryRepository.cs b/API/Data/Repositories/LibraryRepository.cs index dd4d5afa9..d78c5a95d 100644 --- a/API/Data/Repositories/LibraryRepository.cs +++ b/API/Data/Repositories/LibraryRepository.cs @@ -89,6 +89,7 @@ public class LibraryRepository : ILibraryRepository { var library = await GetLibraryForIdAsync(libraryId, LibraryIncludes.Folders | LibraryIncludes.Series); _context.Library.Remove(library); + return await _context.SaveChangesAsync() > 0; } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 3e93abda3..ef97dfc87 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -624,7 +624,7 @@ public class SeriesRepository : ISeriesRepository LastReadingProgress = _context.AppUserProgresses .Where(p => p.Id == progress.Id && p.AppUserId == userId) .Max(p => p.LastModified), - // This is only taking into account chapters that have progress on them, not all chapters in said series + // BUG: This is only taking into account chapters that have progress on them, not all chapters in said series LastChapterCreated = _context.Chapter.Where(c => progress.ChapterId == c.Id).Max(c => c.Created), //LastChapterCreated = _context.Chapter.Where(c => allChapters.Contains(c.Id)).Max(c => c.Created) }); diff --git a/API/Extensions/IdentityServiceExtensions.cs b/API/Extensions/IdentityServiceExtensions.cs index 16404949b..043e5c919 100644 --- a/API/Extensions/IdentityServiceExtensions.cs +++ b/API/Extensions/IdentityServiceExtensions.cs @@ -15,6 +15,12 @@ namespace API.Extensions { public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration config) { + services.Configure(options => + { + options.User.AllowedUserNameCharacters = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+/"; + }); + services.AddIdentityCore(opt => { opt.Password.RequireNonAlphanumeric = false; diff --git a/API/Program.cs b/API/Program.cs index 07d596ff0..3bd895353 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -72,9 +72,8 @@ namespace API } await context.Database.MigrateAsync(); - var roleManager = services.GetRequiredService>(); - await Seed.SeedRoles(roleManager); + await Seed.SeedRoles(services.GetRequiredService>()); await Seed.SeedSettings(context, directoryService); await Seed.SeedThemes(context); await Seed.SeedUserApiKeys(context); @@ -110,7 +109,7 @@ namespace API (await context.ServerSetting.SingleOrDefaultAsync(s => s.Key == ServerSettingKey.InstallVersion))?.Value; } - catch + catch (Exception) { // ignored } diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index 3f82af55d..3146ce6dc 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -164,7 +164,7 @@ public class SeriesService : ISeriesService } await _eventHub.SendMessageAsync(MessageFactory.ScanSeries, - MessageFactory.ScanSeriesEvent(series.Id, series.Name), false); + MessageFactory.ScanSeriesEvent(series.LibraryId, series.Id, series.Name), false); await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries(); diff --git a/API/Services/TaskScheduler.cs b/API/Services/TaskScheduler.cs index 35dc332c6..d749c20ca 100644 --- a/API/Services/TaskScheduler.cs +++ b/API/Services/TaskScheduler.cs @@ -15,7 +15,7 @@ public interface ITaskScheduler Task ScheduleTasks(); Task ScheduleStatsTasks(); void ScheduleUpdaterTasks(); - void ScanLibrary(int libraryId, bool forceUpdate = false); + void ScanLibrary(int libraryId); void CleanupChapters(int[] chapterIds); void RefreshMetadata(int libraryId, bool forceUpdate = true); void RefreshSeriesMetadata(int libraryId, int seriesId, bool forceUpdate = false); @@ -146,7 +146,7 @@ public class TaskScheduler : ITaskScheduler } #endregion - public void ScanLibrary(int libraryId, bool forceUpdate = false) + public void ScanLibrary(int libraryId) { _logger.LogInformation("Enqueuing library scan for: {LibraryId}", libraryId); BackgroundJob.Enqueue(() => _scannerService.ScanLibrary(libraryId)); diff --git a/API/Services/Tasks/Scanner/ParseScannedFiles.cs b/API/Services/Tasks/Scanner/ParseScannedFiles.cs index d9128fbe5..ddefd00e3 100644 --- a/API/Services/Tasks/Scanner/ParseScannedFiles.cs +++ b/API/Services/Tasks/Scanner/ParseScannedFiles.cs @@ -117,10 +117,15 @@ namespace API.Services.Tasks.Scanner } // Patch is SeriesSort from ComicInfo - if (info.ComicInfo != null && !string.IsNullOrEmpty(info.ComicInfo.TitleSort)) + if (!string.IsNullOrEmpty(info.ComicInfo.TitleSort)) { info.SeriesSort = info.ComicInfo.TitleSort; } + + if (!string.IsNullOrEmpty(info.ComicInfo.SeriesSort)) + { + info.SeriesSort = info.ComicInfo.SeriesSort; + } } TrackSeries(info); diff --git a/API/Services/Tasks/ScannerService.cs b/API/Services/Tasks/ScannerService.cs index 78218293b..08c13e338 100644 --- a/API/Services/Tasks/ScannerService.cs +++ b/API/Services/Tasks/ScannerService.cs @@ -162,7 +162,7 @@ public class ScannerService : IScannerService } // Tell UI that this series is done await _eventHub.SendMessageAsync(MessageFactory.ScanSeries, - MessageFactory.ScanSeriesEvent(seriesId, series.Name)); + MessageFactory.ScanSeriesEvent(libraryId, seriesId, series.Name)); await CleanupDbEntities(); BackgroundJob.Enqueue(() => _cacheService.CleanupChapters(chapterIds)); BackgroundJob.Enqueue(() => _metadataService.RefreshMetadataForSeries(libraryId, series.Id, false)); @@ -428,7 +428,7 @@ public class ScannerService : IScannerService foreach (var series in librarySeries) { // This is something more like, the series has finished updating in the backend. It may or may not have been modified. - await _eventHub.SendMessageAsync(MessageFactory.ScanSeries, MessageFactory.ScanSeriesEvent(series.Id, series.Name)); + await _eventHub.SendMessageAsync(MessageFactory.ScanSeries, MessageFactory.ScanSeriesEvent(library.Id, series.Id, series.Name)); } } @@ -523,7 +523,7 @@ public class ScannerService : IScannerService series.Format = parsedInfos[0].Format; } series.OriginalName ??= parsedInfos[0].Series; - if (!series.SortNameLocked) series.SortName ??= parsedInfos[0].SeriesSort; + if (!series.SortNameLocked) series.SortName = parsedInfos[0].SeriesSort; await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, series.Name)); diff --git a/API/Services/Tasks/SiteThemeService.cs b/API/Services/Tasks/SiteThemeService.cs index 4a579434f..e0e1bc2d8 100644 --- a/API/Services/Tasks/SiteThemeService.cs +++ b/API/Services/Tasks/SiteThemeService.cs @@ -100,6 +100,21 @@ public class SiteThemeService : ISiteThemeService await _unitOfWork.CommitAsync(); } + // if there are no default themes, reselect Dark as default + var postSaveThemes = (await _unitOfWork.SiteThemeRepository.GetThemes()).ToList(); + if (!postSaveThemes.Any(t => t.IsDefault)) + { + var defaultThemeName = Seed.DefaultThemes.Single(t => t.IsDefault).NormalizedName; + var theme = postSaveThemes.SingleOrDefault(t => t.NormalizedName == defaultThemeName); + if (theme != null) + { + theme.IsDefault = true; + _unitOfWork.SiteThemeRepository.Update(theme); + await _unitOfWork.CommitAsync(); + } + + } + await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.SiteThemeProgressEvent("", "", ProgressEventType.Ended)); diff --git a/API/Services/Tasks/StatsService.cs b/API/Services/Tasks/StatsService.cs index faf0414b0..831e8f3a0 100644 --- a/API/Services/Tasks/StatsService.cs +++ b/API/Services/Tasks/StatsService.cs @@ -102,11 +102,6 @@ public class StatsService : IStatsService var installId = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallId); var installVersion = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion); - var firstAdminUser = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).First(); - var firstAdminUserPref = (await _unitOfWork.UserRepository.GetPreferencesAsync(firstAdminUser.UserName)); - - var activeTheme = firstAdminUserPref.Theme ?? Seed.DefaultThemes.First(t => t.IsDefault); - var serverInfo = new ServerInfoDto { InstallId = installId.Value, @@ -117,15 +112,24 @@ public class StatsService : IStatsService NumOfCores = Math.Max(Environment.ProcessorCount, 1), HasBookmarks = (await _unitOfWork.UserRepository.GetAllBookmarksAsync()).Any(), NumberOfLibraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).Count(), - ActiveSiteTheme = activeTheme.Name, NumberOfCollections = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync()).Count(), NumberOfReadingLists = await _unitOfWork.ReadingListRepository.Count(), OPDSEnabled = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds, NumberOfUsers = (await _unitOfWork.UserRepository.GetAllUsers()).Count(), TotalFiles = await _unitOfWork.LibraryRepository.GetTotalFiles(), - MangaReaderMode = firstAdminUserPref.ReaderMode }; + var firstAdminUser = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).FirstOrDefault(); + + if (firstAdminUser != null) + { + var firstAdminUserPref = (await _unitOfWork.UserRepository.GetPreferencesAsync(firstAdminUser.UserName)); + var activeTheme = firstAdminUserPref.Theme ?? Seed.DefaultThemes.First(t => t.IsDefault); + + serverInfo.ActiveSiteTheme = activeTheme.Name; + serverInfo.MangaReaderMode = firstAdminUserPref.ReaderMode; + } + return serverInfo; } } diff --git a/API/SignalR/MessageFactory.cs b/API/SignalR/MessageFactory.cs index 6fc6560c2..485a2fc37 100644 --- a/API/SignalR/MessageFactory.cs +++ b/API/SignalR/MessageFactory.cs @@ -80,13 +80,15 @@ namespace API.SignalR public const string LibraryModified = "LibraryModified"; - public static SignalRMessage ScanSeriesEvent(int seriesId, string seriesName) + public static SignalRMessage ScanSeriesEvent(int libraryId, int seriesId, string seriesName) { return new SignalRMessage() { Name = ScanSeries, + EventType = ProgressEventType.Single, Body = new { + LibraryId = libraryId, SeriesId = seriesId, SeriesName = seriesName } diff --git a/API/Startup.cs b/API/Startup.cs index c9cbb32c5..2f8ac4d1a 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -120,6 +120,7 @@ namespace API ForwardedHeaders.All; }); + services.AddHangfire(configuration => configuration .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() @@ -149,7 +150,6 @@ namespace API // Apply all migrations on startup var logger = serviceProvider.GetRequiredService>(); var userManager = serviceProvider.GetRequiredService>(); - var context = serviceProvider.GetRequiredService(); await MigrateBookmarks.Migrate(directoryService, unitOfWork, logger, cacheService); @@ -161,6 +161,7 @@ namespace API var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion); installVersion.Value = BuildInfo.Version.ToString(); unitOfWork.SettingsRepository.Update(installVersion); + await unitOfWork.CommitAsync(); }).GetAwaiter() .GetResult(); diff --git a/UI/Web/src/app/_modals/review-series-modal/review-series-modal.component.html b/UI/Web/src/app/_modals/review-series-modal/review-series-modal.component.html index cfb00e0a8..f64ef951f 100644 --- a/UI/Web/src/app/_modals/review-series-modal/review-series-modal.component.html +++ b/UI/Web/src/app/_modals/review-series-modal/review-series-modal.component.html @@ -12,7 +12,7 @@
- +
diff --git a/UI/Web/src/app/_models/events/scan-series-event.ts b/UI/Web/src/app/_models/events/scan-series-event.ts index f60d82e17..65199aca4 100644 --- a/UI/Web/src/app/_models/events/scan-series-event.ts +++ b/UI/Web/src/app/_models/events/scan-series-event.ts @@ -1,4 +1,5 @@ export interface ScanSeriesEvent { + libraryId: number; seriesId: number; seriesName: string; } \ No newline at end of file diff --git a/UI/Web/src/app/_services/reading-list.service.ts b/UI/Web/src/app/_services/reading-list.service.ts index 45bc86828..e2c5c6104 100644 --- a/UI/Web/src/app/_services/reading-list.service.ts +++ b/UI/Web/src/app/_services/reading-list.service.ts @@ -47,7 +47,7 @@ export class ReadingListService { } updateByMultiple(readingListId: number, seriesId: number, volumeIds: Array, chapterIds?: Array) { - return this.httpClient.post(this.baseUrl + 'readinglist/update-by-multiple', {readingListId, seriesId, volumeIds, chapterIds}); + return this.httpClient.post(this.baseUrl + 'readinglist/update-by-multiple', {readingListId, seriesId, volumeIds, chapterIds}, { responseType: 'text' as 'json' }); } updateByMultipleSeries(readingListId: number, seriesIds: Array) { diff --git a/UI/Web/src/app/_services/series.service.ts b/UI/Web/src/app/_services/series.service.ts index ed6be1b89..e6c8e1cf2 100644 --- a/UI/Web/src/app/_services/series.service.ts +++ b/UI/Web/src/app/_services/series.service.ts @@ -155,7 +155,6 @@ export class SeriesService { } scan(libraryId: number, seriesId: number) { - // TODO: Pipe and put a toaster up: this.toastr.info('Scan queued for ' + series.name); return this.httpClient.post(this.baseUrl + 'series/scan', {libraryId: libraryId, seriesId: seriesId}); } diff --git a/UI/Web/src/app/admin/manage-library/manage-library.component.html b/UI/Web/src/app/admin/manage-library/manage-library.component.html index 2fb30357a..7cde9794d 100644 --- a/UI/Web/src/app/admin/manage-library/manage-library.component.html +++ b/UI/Web/src/app/admin/manage-library/manage-library.component.html @@ -1,16 +1,16 @@

Libraries

-
+
  • {{library.name}}  -
    +
    diff --git a/UI/Web/src/app/admin/manage-library/manage-library.component.ts b/UI/Web/src/app/admin/manage-library/manage-library.component.ts index 698013e2f..19a314396 100644 --- a/UI/Web/src/app/admin/manage-library/manage-library.component.ts +++ b/UI/Web/src/app/admin/manage-library/manage-library.component.ts @@ -2,10 +2,10 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ToastrService } from 'ngx-toastr'; import { Subject } from 'rxjs'; -import { take, takeUntil, takeWhile } from 'rxjs/operators'; +import { distinctUntilChanged, filter, take, takeLast, takeUntil } from 'rxjs/operators'; import { ConfirmService } from 'src/app/shared/confirm.service'; import { NotificationProgressEvent } from 'src/app/_models/events/notification-progress-event'; -import { ProgressEvent } from 'src/app/_models/events/progress-event'; +import { ScanSeriesEvent } from 'src/app/_models/events/scan-series-event'; import { Library, LibraryType } from 'src/app/_models/library'; import { LibraryService } from 'src/app/_services/library.service'; import { EVENTS, Message, MessageHubService } from 'src/app/_services/message-hub.service'; @@ -25,7 +25,6 @@ export class ManageLibraryComponent implements OnInit, OnDestroy { * If a deletion is in progress for a library */ deletionInProgress: boolean = false; - scanInProgress: {[key: number]: {progress: boolean, timestamp?: string}} = {}; libraryTrackBy = (index: number, item: Library) => `${item.name}_${item.lastScanned}_${item.type}_${item.folders.length}`; private readonly onDestroy = new Subject(); @@ -38,29 +37,29 @@ export class ManageLibraryComponent implements OnInit, OnDestroy { this.getLibraries(); // when a progress event comes in, show it on the UI next to library - this.hubService.messages$.pipe(takeUntil(this.onDestroy), takeWhile(event => event.event === EVENTS.NotificationProgress)) - .subscribe((event: Message) => { - if (event.event !== EVENTS.NotificationProgress && (event.payload as NotificationProgressEvent).name === EVENTS.ScanSeries) return; + this.hubService.messages$.pipe(takeUntil(this.onDestroy), + filter(event => event.event === EVENTS.ScanSeries || event.event === EVENTS.NotificationProgress), + distinctUntilChanged((prev: Message, curr: Message) => + this.hasMessageChanged(prev, curr))) + .subscribe((event: Message) => { + console.log('scan event: ', event); + + let libId = 0; + if (event.event === EVENTS.ScanSeries) { + libId = (event.payload as ScanSeriesEvent).libraryId; + } else { + if ((event.payload as NotificationProgressEvent).body.hasOwnProperty('libraryId')) { + libId = (event.payload as NotificationProgressEvent).body.libraryId; + } + } - console.log('scan event: ', event.payload); - // TODO: Refactor this to use EventyType on NotificationProgress interface rather than float comparison - - const scanEvent = event.payload.body as ProgressEvent; - this.scanInProgress[scanEvent.libraryId] = {progress: scanEvent.progress !== 1}; - if (scanEvent.progress === 0) { - this.scanInProgress[scanEvent.libraryId].timestamp = scanEvent.eventTime; - } - - if (this.scanInProgress[scanEvent.libraryId].progress === false && (scanEvent.progress === 1 || event.payload.eventType === 'ended')) { this.libraryService.getLibraries().pipe(take(1)).subscribe(libraries => { - const newLibrary = libraries.find(lib => lib.id === scanEvent.libraryId); - const existingLibrary = this.libraries.find(lib => lib.id === scanEvent.libraryId); + const newLibrary = libraries.find(lib => lib.id === libId); + const existingLibrary = this.libraries.find(lib => lib.id === libId); if (existingLibrary !== undefined) { existingLibrary.lastScanned = newLibrary?.lastScanned || existingLibrary.lastScanned; } }); - } - }); } @@ -69,6 +68,17 @@ export class ManageLibraryComponent implements OnInit, OnDestroy { this.onDestroy.complete(); } + hasMessageChanged(prev: Message, curr: Message) { + if (curr.event !== prev.event) return true; + if (curr.event === EVENTS.ScanSeries) { + return (prev.payload as ScanSeriesEvent).libraryId === (curr.payload as ScanSeriesEvent).libraryId; + } + if (curr.event === EVENTS.NotificationProgress) { + return (prev.payload as NotificationProgressEvent).eventType != (curr.payload as NotificationProgressEvent).eventType; + } + return false; + } + getLibraries() { this.loading = true; this.libraryService.getLibraries().pipe(take(1)).subscribe(libraries => { diff --git a/UI/Web/src/app/admin/manage-users/manage-users.component.ts b/UI/Web/src/app/admin/manage-users/manage-users.component.ts index 454e9786e..acae4b78c 100644 --- a/UI/Web/src/app/admin/manage-users/manage-users.component.ts +++ b/UI/Web/src/app/admin/manage-users/manage-users.component.ts @@ -74,6 +74,7 @@ export class ManageUsersComponent implements OnInit, OnDestroy { } loadPendingInvites() { + this.pendingInvites = []; this.memberService.getPendingInvites().subscribe(members => { this.pendingInvites = members; // Show logged in user at the top of the list @@ -116,9 +117,7 @@ export class ManageUsersComponent implements OnInit, OnDestroy { inviteUser() { const modalRef = this.modalService.open(InviteUserComponent, {size: 'lg'}); modalRef.closed.subscribe((successful: boolean) => { - if (successful) { - this.loadPendingInvites(); - } + this.loadPendingInvites(); }); } diff --git a/UI/Web/src/app/all-series/all-series.component.html b/UI/Web/src/app/all-series/all-series.component.html index e10933be9..b47e3258b 100644 --- a/UI/Web/src/app/all-series/all-series.component.html +++ b/UI/Web/src/app/all-series/all-series.component.html @@ -1,4 +1,4 @@ - +

    All Series 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 75b3fd9dc..22356d552 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 @@ -41,7 +41,7 @@

    - +
    @@ -52,7 +52,7 @@ 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 - +
    @@ -70,13 +70,13 @@
    - -
    {{pageNum}}
    -
    + +
    {{pageNum}}
    +
    -
    {{maxPages - 1}}
    - +
    {{maxPages - 1}}
    +

    Table of Contents

    @@ -127,11 +127,11 @@ [disabled]="IsPrevDisabled" title="{{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}} Page"> -  {{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}} +  {{readingDirection === ReadingDirection.LeftToRight ? 'Previous' : 'Next'}} - - -
    + + +
    Loading book... @@ -142,12 +142,12 @@ (Incognito Mode)
    - +
    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 395ce39fb..e8657e2aa 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 @@ -1013,8 +1013,9 @@ export class BookReaderComponent implements OnInit, AfterViewInit, OnDestroy { } if (element === null) return; - - this.scrollService.scrollTo(element.getBoundingClientRect().top + window.pageYOffset + TOP_OFFSET, this.reader.nativeElement); + 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() { diff --git a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html index a3cb6dfdd..3bab2708c 100644 --- a/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html +++ b/UI/Web/src/app/cards/_modals/card-details-modal/card-details-modal.component.html @@ -96,6 +96,9 @@ + + No bookmarks yet +

  • diff --git a/UI/Web/src/app/cards/chapter-metadata-detail/chapter-metadata-detail.component.html b/UI/Web/src/app/cards/chapter-metadata-detail/chapter-metadata-detail.component.html index 349f2e746..32d2617d5 100644 --- a/UI/Web/src/app/cards/chapter-metadata-detail/chapter-metadata-detail.component.html +++ b/UI/Web/src/app/cards/chapter-metadata-detail/chapter-metadata-detail.component.html @@ -8,7 +8,7 @@ No metadata available
    -
    +
    Writers
    @@ -17,7 +17,7 @@
    -
    +
    Cover Artists
    @@ -26,7 +26,7 @@
    -
    +
    Pencillers
    @@ -35,7 +35,7 @@
    -
    +
    Inkers
    @@ -44,7 +44,7 @@
    -
    +
    Colorists
    @@ -54,7 +54,7 @@
    -
    +
    Letterers
    @@ -63,7 +63,7 @@
    -
    +
    Editors
    @@ -72,7 +72,7 @@
    -
    +
    Publishers
    @@ -81,7 +81,7 @@
    -
    +
    Characters
    @@ -89,7 +89,7 @@
    -
    +
    Translators
    diff --git a/UI/Web/src/app/collections/collection-detail/collection-detail.component.html b/UI/Web/src/app/collections/collection-detail/collection-detail.component.html index 9304abcb0..ad90f0c00 100644 --- a/UI/Web/src/app/collections/collection-detail/collection-detail.component.html +++ b/UI/Web/src/app/collections/collection-detail/collection-detail.component.html @@ -1,17 +1,17 @@ - + -

    +

    - {{collectionTag.title}} + {{collectionTag.title}} ()

    -
    +
    -
    +
    -
    +
    @@ -24,6 +24,7 @@ [items]="series" [pagination]="seriesPagination" [filterSettings]="filterSettings" + [filterOpen]="filterOpen" (pageChange)="onPageChange($event)" (applyFilter)="updateFilter($event)" > diff --git a/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts b/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts index 27d302bd7..0a8e768d2 100644 --- a/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts +++ b/UI/Web/src/app/collections/collection-detail/collection-detail.component.ts @@ -1,4 +1,4 @@ -import { Component, HostListener, OnDestroy, OnInit } from '@angular/core'; +import { Component, EventEmitter, HostListener, OnDestroy, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { Router, ActivatedRoute } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -43,6 +43,8 @@ export class CollectionDetailComponent implements OnInit, OnDestroy { summary: string = ''; actionInProgress: boolean = false; + + filterOpen: EventEmitter = new EventEmitter(); private onDestory: Subject = new Subject(); @@ -102,6 +104,7 @@ export class CollectionDetailComponent implements OnInit, OnDestroy { return; } const tagId = parseInt(routeId, 10); + this.seriesPagination = {currentPage: 0, itemsPerPage: 30, totalItems: 0, totalPages: 1}; [this.filterSettings.presets, this.filterSettings.openByDefault] = this.utilityService.filterPresetsFromUrl(this.route.snapshot, this.seriesService.createSeriesFilter()); this.filterSettings.presets.collectionTags = [tagId]; @@ -161,16 +164,22 @@ export class CollectionDetailComponent implements OnInit, OnDestroy { onPageChange(pagination: Pagination) { this.router.navigate(['collections', this.collectionTag.id], {replaceUrl: true, queryParamsHandling: 'merge', queryParams: {page: this.seriesPagination.currentPage} }); + this.loadPage(); } loadPage() { - const page = this.route.snapshot.queryParamMap.get('page'); + const page = this.getPage(); if (page != null) { - if (this.seriesPagination === undefined || this.seriesPagination === null) { - this.seriesPagination = {currentPage: 0, itemsPerPage: 30, totalItems: 0, totalPages: 1}; - } this.seriesPagination.currentPage = parseInt(page, 10); } + + // The filter is out of sync with the presets from typeaheads on first load but syncs afterwards + if (this.filter == undefined) { + this.filter = this.seriesService.createSeriesFilter(); + this.filter.collectionTags.push(this.collectionTag.id); + } + + // TODO: Add ability to filter series for a collection // Reload page after a series is updated or first load this.seriesService.getSeriesForTag(this.collectionTag.id, this.seriesPagination?.currentPage, this.seriesPagination?.itemsPerPage).subscribe(tags => { this.series = tags.result; @@ -180,9 +189,10 @@ export class CollectionDetailComponent implements OnInit, OnDestroy { }); } - updateFilter(data: FilterEvent) { - this.filter = data.filter; - if (this.seriesPagination !== undefined && this.seriesPagination !== null && !data.isFirst) { + updateFilter(event: FilterEvent) { + this.filter = event.filter; + const page = this.getPage(); + if (page === undefined || page === null || !event.isFirst) { this.seriesPagination.currentPage = 1; this.onPageChange(this.seriesPagination); } else { @@ -190,6 +200,11 @@ export class CollectionDetailComponent implements OnInit, OnDestroy { } } + getPage() { + const urlParams = new URLSearchParams(window.location.search); + return urlParams.get('page'); + } + handleCollectionActionCallback(action: Action, collectionTag: CollectionTag) { switch (action) { case(Action.Edit): diff --git a/UI/Web/src/app/events-widget/events-widget.component.scss b/UI/Web/src/app/events-widget/events-widget.component.scss index de94c5393..6a2070e78 100644 --- a/UI/Web/src/app/events-widget/events-widget.component.scss +++ b/UI/Web/src/app/events-widget/events-widget.component.scss @@ -1,14 +1,12 @@ -// NOTE: I'm leaving this not fully customized because I'm planning to rewrite the whole design in v0.5.2/3 -// These are customizations for events nav .dark-menu { background-color: var(--navbar-bg-color); - border-color: rgba(1, 4, 9, 0.5); + border-color: var(--event-widget-border-color); // rgba(1, 4, 9, 0.5); } .dark-menu-item { - color: var(--body-text-color); - background-color: rgb(1, 4, 9); - border-color: rgba(53, 53, 53, 0.5); + color: var(--event-widget-text-color); + background-color: var(--event-widget-item-bg-color); // rgb(1, 4, 9) + border-color: var(--event-widget-item-border-color); // rgba(53, 53, 53, 0.5) } // Popovers need to be their own component diff --git a/UI/Web/src/app/library-detail/library-detail.component.html b/UI/Web/src/app/library-detail/library-detail.component.html index 621312adf..6b3771c0d 100644 --- a/UI/Web/src/app/library-detail/library-detail.component.html +++ b/UI/Web/src/app/library-detail/library-detail.component.html @@ -1,4 +1,4 @@ - +

    {{libraryName}} diff --git a/UI/Web/src/app/recently-added/recently-added.component.html b/UI/Web/src/app/recently-added/recently-added.component.html index 6e707fd7d..06b744c22 100644 --- a/UI/Web/src/app/recently-added/recently-added.component.html +++ b/UI/Web/src/app/recently-added/recently-added.component.html @@ -1,4 +1,4 @@ - +

    Recently Added

    diff --git a/UI/Web/src/app/registration/confirm-email/confirm-email.component.html b/UI/Web/src/app/registration/confirm-email/confirm-email.component.html index c829fad68..635756b35 100644 --- a/UI/Web/src/app/registration/confirm-email/confirm-email.component.html +++ b/UI/Web/src/app/registration/confirm-email/confirm-email.component.html @@ -22,7 +22,7 @@
    - +
    This field is required diff --git a/UI/Web/src/app/registration/confirm-email/confirm-email.component.scss b/UI/Web/src/app/registration/confirm-email/confirm-email.component.scss index b3a96fcce..4d637eb32 100644 --- a/UI/Web/src/app/registration/confirm-email/confirm-email.component.scss +++ b/UI/Web/src/app/registration/confirm-email/confirm-email.component.scss @@ -1,4 +1,4 @@ input { background-color: #fff !important; - color: black; + color: black !important; } \ No newline at end of file diff --git a/UI/Web/src/app/registration/confirm-email/confirm-email.component.ts b/UI/Web/src/app/registration/confirm-email/confirm-email.component.ts index f6e385e7a..3ea2c501e 100644 --- a/UI/Web/src/app/registration/confirm-email/confirm-email.component.ts +++ b/UI/Web/src/app/registration/confirm-email/confirm-email.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; import { ThemeService } from 'src/app/theme.service'; import { AccountService } from 'src/app/_services/account.service'; +import { NavService } from 'src/app/_services/nav.service'; @Component({ selector: 'app-confirm-email', @@ -29,8 +30,9 @@ export class ConfirmEmailComponent implements OnInit { constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, - private toastr: ToastrService, private themeService: ThemeService) { - this.themeService.setTheme(this.themeService.defaultTheme); + private toastr: ToastrService, private themeService: ThemeService, private navService: NavService) { + this.navService.hideSideNav(); + this.themeService.setTheme(this.themeService.defaultTheme); const token = this.route.snapshot.queryParamMap.get('token'); const email = this.route.snapshot.queryParamMap.get('email'); if (token == undefined || token === '' || token === null) { diff --git a/UI/Web/src/app/registration/register/register.component.scss b/UI/Web/src/app/registration/register/register.component.scss index b3a96fcce..e09652950 100644 --- a/UI/Web/src/app/registration/register/register.component.scss +++ b/UI/Web/src/app/registration/register/register.component.scss @@ -1,4 +1,4 @@ input { background-color: #fff !important; - color: black; -} \ No newline at end of file + color: black !important; +} diff --git a/UI/Web/src/app/registration/register/register.component.ts b/UI/Web/src/app/registration/register/register.component.ts index 76ac3d87d..bc5f0454e 100644 --- a/UI/Web/src/app/registration/register/register.component.ts +++ b/UI/Web/src/app/registration/register/register.component.ts @@ -5,6 +5,7 @@ import { ToastrService } from 'ngx-toastr'; import { take } from 'rxjs/operators'; import { AccountService } from 'src/app/_services/account.service'; import { MemberService } from 'src/app/_services/member.service'; +import { NavService } from 'src/app/_services/nav.service'; /** * This is exclusivly used to register the first user on the server and nothing else @@ -22,10 +23,12 @@ export class RegisterComponent implements OnInit { password: new FormControl('', [Validators.required, Validators.maxLength(32), Validators.minLength(6)]), }); - constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, private toastr: ToastrService, private memberService: MemberService) { + constructor(private route: ActivatedRoute, private router: Router, private accountService: AccountService, + private toastr: ToastrService, private memberService: MemberService) { this.memberService.adminExists().pipe(take(1)).subscribe(adminExists => { if (adminExists) { this.router.navigateByUrl('login'); + return; } }); } diff --git a/UI/Web/src/app/registration/reset-password/reset-password.component.ts b/UI/Web/src/app/registration/reset-password/reset-password.component.ts index 4080b4f63..06e9e775e 100644 --- a/UI/Web/src/app/registration/reset-password/reset-password.component.ts +++ b/UI/Web/src/app/registration/reset-password/reset-password.component.ts @@ -25,6 +25,8 @@ export class ResetPasswordComponent implements OnInit { this.accountService.requestResetPasswordEmail(model).subscribe((resp: string) => { this.toastr.info(resp); this.router.navigateByUrl('login'); + }, err => { + this.toastr.error(err.error); }); } diff --git a/UI/Web/src/app/series-detail/series-detail.component.html b/UI/Web/src/app/series-detail/series-detail.component.html index c9749f5f7..8fee5c5c8 100644 --- a/UI/Web/src/app/series-detail/series-detail.component.html +++ b/UI/Web/src/app/series-detail/series-detail.component.html @@ -9,10 +9,10 @@
    {{series?.localizedName}}
    -
    +
    -
    - +
    +
    @@ -21,7 +21,7 @@ -  {{(hasReadingProgress) ? 'Continue' : 'Read'}} +  {{(hasReadingProgress) ? 'Continue' : 'Read'}}
    @@ -31,7 +31,7 @@
    -
    +
    diff --git a/UI/Web/src/app/series-detail/series-detail.component.scss b/UI/Web/src/app/series-detail/series-detail.component.scss index 4c204f40a..472579f96 100644 --- a/UI/Web/src/app/series-detail/series-detail.component.scss +++ b/UI/Web/src/app/series-detail/series-detail.component.scss @@ -5,21 +5,4 @@ .rating-star { margin-top: 2px; font-size: 1.5rem; -} - -.poster { - width: 100%; - max-width: 300px; -} - -@media(max-width: var(--grid-breakpoints-sm)) { - .poster { - display: none; - } -} - -@media(max-width: var(--grid-breakpoints-sm)) { - .read-btn--text { - display: none; - } -} +} \ No newline at end of file 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 fd6f7fb02..bdd7a411e 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 @@ -15,6 +15,11 @@ export class SideNavCompanionBarComponent implements OnInit { */ @Input() hasFilter: boolean = false; + /** + * Is the input open by default + */ + @Input() filterOpenByDefault: boolean = false; + /** * Should be passed through from Filter component. */ @@ -27,14 +32,10 @@ export class SideNavCompanionBarComponent implements OnInit { constructor() { } ngOnInit(): void { - } - - goBack() { - + this.isFilterOpen = this.filterOpenByDefault; } toggleFilter() { - //collapse.toggle() this.isFilterOpen = !this.isFilterOpen; this.filterOpen.emit(this.isFilterOpen); } diff --git a/UI/Web/src/app/sidenav/side-nav-item/side-nav-item.component.scss b/UI/Web/src/app/sidenav/side-nav-item/side-nav-item.component.scss index 30a29baed..621f64623 100644 --- a/UI/Web/src/app/sidenav/side-nav-item/side-nav-item.component.scss +++ b/UI/Web/src/app/sidenav/side-nav-item/side-nav-item.component.scss @@ -59,8 +59,13 @@ } &:hover { - color: var(--side-nav-hover-color); + color: var(--side-nav-hover-text-color); background-color: var(--side-nav-hover-bg-color); + + .card-actions i.fa { + // TODO: The override to white does not work, please fix for light themes + color: var(--side-nav-hover-text-color) !important; + } } &.active { @@ -76,13 +81,20 @@ } .side-nav-text, i { - color: var(--side-nav-item-active-text-color); + + color: var(--side-nav-item-active-text-color) !important; } &:hover { - color: var(--side-nav-hover-color); + color: var(--side-nav-hover-text-color); background-color: var(--side-nav-hover-bg-color); + + .card-actions i.fa { + // TODO: The override to white does not work, please fix for light themes + color: var(--side-nav-hover-text-color) !important; + } } + } } diff --git a/UI/Web/src/app/sidenav/side-nav/side-nav.component.ts b/UI/Web/src/app/sidenav/side-nav/side-nav.component.ts index 5ce818d87..feebe6f26 100644 --- a/UI/Web/src/app/sidenav/side-nav/side-nav.component.ts +++ b/UI/Web/src/app/sidenav/side-nav/side-nav.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Observable, Subject } from 'rxjs'; -import { take, takeUntil, takeWhile } from 'rxjs/operators'; +import { filter, take, takeUntil, takeWhile } from 'rxjs/operators'; import { EVENTS, MessageHubService } from 'src/app/_services/message-hub.service'; import { UtilityService } from '../../shared/_services/utility.service'; import { Library } from '../../_models/library'; @@ -48,7 +48,8 @@ export class SideNavComponent implements OnInit, OnDestroy { this.actions = this.actionFactoryService.getLibraryActions(this.handleAction.bind(this)); }); - this.messageHub.messages$.pipe(takeUntil(this.onDestory), takeWhile(event => event.event === EVENTS.LibraryModified)).subscribe(event => { + this.messageHub.messages$.pipe(takeUntil(this.onDestory), filter(event => event.event === EVENTS.LibraryModified)).subscribe(event => { + console.log('Received library modfied event'); this.libraryService.getLibrariesForMember().pipe(take(1)).subscribe((libraries: Library[]) => { this.libraries = libraries; }); diff --git a/UI/Web/src/app/typeahead/typeahead.component.ts b/UI/Web/src/app/typeahead/typeahead.component.ts index 967469c40..43c8ab681 100644 --- a/UI/Web/src/app/typeahead/typeahead.component.ts +++ b/UI/Web/src/app/typeahead/typeahead.component.ts @@ -223,7 +223,6 @@ export class TypeaheadComponent implements OnInit, OnDestroy { switchMap(val => { this.isLoadingOptions = true; let results: Observable; - console.log('val: ', val); if (Array.isArray(this.settings.fetchFn)) { const filteredArray = this.settings.compareFn(this.settings.fetchFn, val.trim()); results = of(filteredArray).pipe(takeUntil(this.onDestroy), map((items: any[]) => items.filter(item => this.filterSelected(item)))); @@ -457,9 +456,6 @@ export class TypeaheadComponent implements OnInit, OnDestroy { if (this.showAddItem) { this.hasFocus = true; } - console.log('show Add item: ', this.showAddItem); - console.log('compare func: ', this.settings.compareFn(options, this.typeaheadControl.value.trim())); - } unlock(event: any) { diff --git a/UI/Web/src/app/user-login/user-login.component.html b/UI/Web/src/app/user-login/user-login.component.html index 966ecf7c1..93354e7dc 100644 --- a/UI/Web/src/app/user-login/user-login.component.html +++ b/UI/Web/src/app/user-login/user-login.component.html @@ -15,7 +15,7 @@
    diff --git a/UI/Web/src/theme/themes/dark.scss b/UI/Web/src/theme/themes/dark.scss index e781019f2..82e41590b 100644 --- a/UI/Web/src/theme/themes/dark.scss +++ b/UI/Web/src/theme/themes/dark.scss @@ -94,7 +94,7 @@ --side-nav-openclose-transition: 0.15s ease-in-out; --side-nav-box-shadow: rgba(0,0,0,0.5); --side-nav-mobile-box-shadow: 3px 0em 5px 10em rgb(0 0 0 / 50%); - --side-nav-hover-color: white; + --side-nav-hover-text-color: white; --side-nav-hover-bg-color: black; --side-nav-color: white; --side-nav-border-radius: 5px; @@ -210,5 +210,12 @@ --carousel-hover-header-text-decoration: none; /** Drawer */ - --drawer-background-color: black; + --drawer-background-color: black; + + /** Event Widget */ + --event-widget-bg-color: rgb(1, 4, 9); + --event-widget-item-bg-color: rgb(1, 4, 9); + --event-widget-text-color: var(--body-text-color); + --event-widget-item-border-color: rgba(53, 53, 53, 0.5); + --event-widget-border-color: rgba(1, 4, 9, 0.5); } diff --git a/UI/Web/src/theme/themes/e-ink.scss b/UI/Web/src/theme/themes/e-ink.scss index 82e2b7151..51080dc29 100644 --- a/UI/Web/src/theme/themes/e-ink.scss +++ b/UI/Web/src/theme/themes/e-ink.scss @@ -37,6 +37,7 @@ --btn-disabled-bg-color: #020202; --btn-disabled-text-color: white; --btn-disabled-border-color: #6c757d; + --btn-fa-icon-color: black; /* Nav */ @@ -65,7 +66,7 @@ --side-nav-openclose-transition: 1ms; --side-nav-box-shadow: none; --side-nav-mobile-box-shadow: 3px 0em 5px 10em rgb(0 0 0 / 50%); - --side-nav-hover-color: white; + --side-nav-hover-text-color: white; --side-nav-hover-bg-color: black; --side-nav-color: black; --side-nav-border-radius: 5px; @@ -78,7 +79,7 @@ --side-nav-item-active-color: var(--primary-color); --side-nav-active-bg-color: rgba(0,0,0,0.5); --side-nav-overlay-color: rgba(0,0,0,1); - --side-nav-item-active-text-color: black; + --side-nav-item-active-text-color: white; /* Toasts */ --toast-success-bg-color: rgba(74, 198, 148, 0.9); @@ -156,5 +157,19 @@ --pagination-focus-border-color: var(--primary-color); --pagination-link-hover-color: var(--primary-color); + /** Event Widget */ + --event-widget-bg-color: white; + --event-widget-item-bg-color: lightgrey; + --event-widget-text-color: black; + --event-widget-item-border-color: lightgrey; + --event-widget-border-color: lightgrey; + + /* Popover */ + --popover-body-bg-color: var(--navbar-bg-color); + --popover-body-text-color: var(--navbar-text-color); + --popover-outerarrow-color: lightgrey; + --popover-arrow-color: lightgrey; + --popover-bg-color: lightgrey; + --popover-border-color: lightgrey; } \ No newline at end of file diff --git a/UI/Web/src/theme/themes/light.scss b/UI/Web/src/theme/themes/light.scss index 734bc6e5f..d8b040e16 100644 --- a/UI/Web/src/theme/themes/light.scss +++ b/UI/Web/src/theme/themes/light.scss @@ -23,11 +23,20 @@ /* Buttons */ --btn-primary-text-color: black; + --btn-primary-bg-color: white; + --btn-primary-border-color: black; + --btn-primary-hover-text-color: white; + --btn-primary-hover-bg-color: black; + --btn-primary-hover-border-color: black; --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(68 79 117 / 50%); + --btn-fa-icon-color: black; + --btn-disabled-bg-color: #020202; + --btn-disabled-text-color: white; + --btn-disabled-border-color: #6c757d; /* Nav */ --nav-link-active-text-color: white; @@ -46,7 +55,7 @@ --side-nav-openclose-transition: 0.15s ease-in-out; --side-nav-box-shadow: none; --side-nav-mobile-box-shadow: 3px 0em 5px 10em rgb(0 0 0 / 50%); - --side-nav-hover-color: white; + --side-nav-hover-text-color: white; --side-nav-hover-bg-color: black; --side-nav-color: black; --side-nav-border-radius: 5px; @@ -58,12 +67,12 @@ --side-nav-closed-bg-color: transparent; --side-nav-item-active-color: var(--primary-color); --side-nav-active-bg-color: rgba(0,0,0,0.5); - --side-nav-item-active-text-color: black; + --side-nav-item-active-text-color: white; --side-nav-overlay-color: rgba(0,0,0,0.5); /* Checkboxes */ - --checkbox-checked-bg-color: black; + --checkbox-checked-bg-color: var(--primary-color); --checkbox-bg-color: white; --checkbox-border-color: var(--primary-color); --checkbox-focus-border-color: var(--input-border-color); @@ -148,4 +157,19 @@ --pagination-link-bg-color: white; --pagination-focus-border-color: var(--primary-color); --pagination-link-hover-color: var(--primary-color); + + /** Event Widget */ + --event-widget-bg-color: white; + --event-widget-item-bg-color: lightgrey; + --event-widget-text-color: black; + --event-widget-item-border-color: lightgrey; + --event-widget-border-color: lightgrey; + + /* Popover */ + --popover-body-bg-color: var(--navbar-bg-color); + --popover-body-text-color: var(--navbar-text-color); + --popover-outerarrow-color: lightgrey; + --popover-arrow-color: lightgrey; + --popover-bg-color: lightgrey; + --popover-border-color: lightgrey; } \ No newline at end of file