diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index e997a9e8d..ec7837fdc 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -230,7 +230,7 @@ jobs: severity: info description: v${{steps.get-version.outputs.assembly-version}} - ${{ steps.findPr.outputs.title }} details: '${{ steps.parse-body.outputs.BODY }}' - text: <@&950058626658234398> A new nightly build has been released for docker. + text: <@&939225459156217917> <@&939225350775406643> A new nightly build has been released for docker. webhookUrl: ${{ secrets.DISCORD_DOCKER_UPDATE_URL }} stable: diff --git a/API.Tests/Services/ReaderServiceTests.cs b/API.Tests/Services/ReaderServiceTests.cs index 61fad6e0a..a66863487 100644 --- a/API.Tests/Services/ReaderServiceTests.cs +++ b/API.Tests/Services/ReaderServiceTests.cs @@ -1650,61 +1650,6 @@ public class ReaderServiceTests Assert.Equal("Some Special Title", nextChapter.Range); } - [Fact] - public async Task GetContinuePoint_ShouldReturnFirstVolumeChapter_WhenPreExistingProgress() - { - var series = new Series() - { - Name = "Test", - Library = new Library() - { - Name = "Test LIb", - Type = LibraryType.Manga, - }, - Volumes = new List() - { - EntityFactory.CreateVolume("0", new List() - { - EntityFactory.CreateChapter("230", false, new List(), 1), - //EntityFactory.CreateChapter("231", false, new List(), 1), (added later) - }), - EntityFactory.CreateVolume("1", new List() - { - EntityFactory.CreateChapter("1", false, new List(), 1), - EntityFactory.CreateChapter("2", false, new List(), 1), - }), - EntityFactory.CreateVolume("2", new List() - { - EntityFactory.CreateChapter("0", false, new List(), 1), - //EntityFactory.CreateChapter("14.9", false, new List(), 1), (added later) - }), - } - }; - _context.Series.Add(series); - - _context.AppUser.Add(new AppUser() - { - UserName = "majora2007" - }); - - await _context.SaveChangesAsync(); - - - var readerService = new ReaderService(_unitOfWork, Substitute.For>()); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); - await readerService.MarkSeriesAsRead(user, 1); - await _context.SaveChangesAsync(); - - // Add 2 new unread series to the Series - series.Volumes[0].Chapters.Add(EntityFactory.CreateChapter("231", false, new List(), 1)); - series.Volumes[2].Chapters.Add(EntityFactory.CreateChapter("14.9", false, new List(), 1)); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); - - var nextChapter = await readerService.GetContinuePoint(1, 1); - Assert.Equal("14.9", nextChapter.Range); - } - #endregion #region MarkChaptersUntilAsRead @@ -1838,126 +1783,5 @@ public class ReaderServiceTests #endregion - #region MarkSeriesAsRead - [Fact] - public async Task MarkSeriesAsReadTest() - { - await ResetDB(); - - _context.Series.Add(new Series() - { - Name = "Test", - Library = new Library() { - Name = "Test LIb", - Type = LibraryType.Manga, - }, - Volumes = new List() - { - new Volume() - { - Chapters = new List() - { - new Chapter() - { - Pages = 1 - }, - new Chapter() - { - Pages = 2 - } - } - }, - new Volume() - { - Chapters = new List() - { - new Chapter() - { - Pages = 1 - }, - new Chapter() - { - Pages = 2 - } - } - } - } - }); - - _context.AppUser.Add(new AppUser() - { - UserName = "majora2007" - }); - - await _context.SaveChangesAsync(); - - var readerService = new ReaderService(_unitOfWork, Substitute.For>()); - - await readerService.MarkSeriesAsRead(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1); - await _context.SaveChangesAsync(); - - Assert.Equal(4, (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); - } - - - #endregion - - #region MarkSeriesAsUnread - - [Fact] - public async Task MarkSeriesAsUnreadTest() - { - await ResetDB(); - - _context.Series.Add(new Series() - { - Name = "Test", - Library = new Library() { - Name = "Test LIb", - Type = LibraryType.Manga, - }, - Volumes = new List() - { - new Volume() - { - Chapters = new List() - { - new Chapter() - { - Pages = 1 - }, - new Chapter() - { - Pages = 2 - } - } - } - } - }); - - _context.AppUser.Add(new AppUser() - { - UserName = "majora2007" - }); - - await _context.SaveChangesAsync(); - - var readerService = new ReaderService(_unitOfWork, Substitute.For>()); - - var volumes = (await _unitOfWork.VolumeRepository.GetVolumes(1)).ToList(); - readerService.MarkChaptersAsRead(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes.First().Chapters); - - await _context.SaveChangesAsync(); - Assert.Equal(2, (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); - - await readerService.MarkSeriesAsUnread(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1); - await _context.SaveChangesAsync(); - - var progresses = (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses; - Assert.Equal(0, progresses.Max(p => p.PagesRead)); - Assert.Equal(2, progresses.Count); - } - - #endregion } diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs index 7fc8769e7..913f53133 100644 --- a/API/Controllers/AccountController.cs +++ b/API/Controllers/AccountController.cs @@ -338,12 +338,6 @@ namespace API.Controllers - /// - /// Invites a user to the server. Will generate a setup link for continuing setup. If the server is not accessible, no - /// email will be sent. - /// - /// - /// [Authorize(Policy = "RequireAdminRole")] [HttpPost("invite")] public async Task> InviteUser(InviteUserDto dto) @@ -423,9 +417,7 @@ namespace API.Controllers var emailLink = GenerateEmailLink(token, "confirm-email", dto.Email); _logger.LogCritical("[Invite User]: Email Link for {UserName}: {Link}", user.UserName, emailLink); - var host = _environment.IsDevelopment() ? "localhost:4200" : Request.Host.ToString(); - var accessible = await _emailService.CheckIfAccessible(host); - if (accessible) + if (dto.SendEmail) { await _emailService.SendConfirmationEmail(new ConfirmationEmailDto() { @@ -434,11 +426,7 @@ namespace API.Controllers ServerConfirmationLink = emailLink }); } - return Ok(new InviteUserResponse - { - EmailLink = emailLink, - EmailSent = accessible - }); + return Ok(emailLink); } catch (Exception) { diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index fa3be9c05..a8a34bdfd 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -109,7 +109,14 @@ namespace API.Controllers public async Task MarkRead(MarkReadDto markReadDto) { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress); - await _readerService.MarkSeriesAsRead(user, markReadDto.SeriesId); + var volumes = await _unitOfWork.VolumeRepository.GetVolumes(markReadDto.SeriesId); + user.Progresses ??= new List(); + foreach (var volume in volumes) + { + _readerService.MarkChaptersAsRead(user, markReadDto.SeriesId, volume.Chapters); + } + + _unitOfWork.UserRepository.Update(user); if (await _unitOfWork.CommitAsync()) { @@ -130,7 +137,14 @@ namespace API.Controllers public async Task MarkUnread(MarkReadDto markReadDto) { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress); - await _readerService.MarkSeriesAsUnread(user, markReadDto.SeriesId); + var volumes = await _unitOfWork.VolumeRepository.GetVolumes(markReadDto.SeriesId); + user.Progresses ??= new List(); + foreach (var volume in volumes) + { + _readerService.MarkChaptersAsUnread(user, markReadDto.SeriesId, volume.Chapters); + } + + _unitOfWork.UserRepository.Update(user); if (await _unitOfWork.CommitAsync()) { diff --git a/API/DTOs/Account/InviteUserDto.cs b/API/DTOs/Account/InviteUserDto.cs index 42d4bdf8e..04c9c1103 100644 --- a/API/DTOs/Account/InviteUserDto.cs +++ b/API/DTOs/Account/InviteUserDto.cs @@ -16,4 +16,6 @@ public class InviteUserDto /// A list of libraries to grant access to /// public IList Libraries { get; init; } + + public bool SendEmail { get; init; } = true; } diff --git a/API/DTOs/Account/InviteUserResponse.cs b/API/DTOs/Account/InviteUserResponse.cs deleted file mode 100644 index 9387b5492..000000000 --- a/API/DTOs/Account/InviteUserResponse.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace API.DTOs.Account; - -public class InviteUserResponse -{ - /// - /// Email link used to setup the user account - /// - public string EmailLink { get; set; } - /// - /// Was an email sent (ie is this server accessible) - /// - public bool EmailSent { get; set; } -} diff --git a/API/DTOs/Filtering/FilterDto.cs b/API/DTOs/Filtering/FilterDto.cs index 90ad52759..fba9a7493 100644 --- a/API/DTOs/Filtering/FilterDto.cs +++ b/API/DTOs/Filtering/FilterDto.cs @@ -25,51 +25,51 @@ namespace API.DTOs.Filtering /// public IList Genres { get; init; } = new List(); /// - /// A list of Writers to restrict search to. Defaults to all Writers by passing an empty list + /// A list of Writers to restrict search to. Defaults to all genres by passing an empty list /// public IList Writers { get; init; } = new List(); /// - /// A list of Penciller ids to restrict search to. Defaults to all Pencillers by passing an empty list + /// A list of Penciller ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Penciller { get; init; } = new List(); /// - /// A list of Inker ids to restrict search to. Defaults to all Inkers by passing an empty list + /// A list of Inker ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Inker { get; init; } = new List(); /// - /// A list of Colorist ids to restrict search to. Defaults to all Colorists by passing an empty list + /// A list of Colorist ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Colorist { get; init; } = new List(); /// - /// A list of Letterer ids to restrict search to. Defaults to all Letterers by passing an empty list + /// A list of Letterer ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Letterer { get; init; } = new List(); /// - /// A list of CoverArtist ids to restrict search to. Defaults to all CoverArtists by passing an empty list + /// A list of CoverArtist ids to restrict search to. Defaults to all genres by passing an empty list /// public IList CoverArtist { get; init; } = new List(); /// - /// A list of Editor ids to restrict search to. Defaults to all Editors by passing an empty list + /// A list of Editor ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Editor { get; init; } = new List(); /// - /// A list of Publisher ids to restrict search to. Defaults to all Publishers by passing an empty list + /// A list of Publisher ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Publisher { get; init; } = new List(); /// - /// A list of Character ids to restrict search to. Defaults to all Characters by passing an empty list + /// A list of Character ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Character { get; init; } = new List(); /// - /// A list of Translator ids to restrict search to. Defaults to all Translatorss by passing an empty list + /// A list of Translator ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Translators { get; init; } = new List(); /// - /// A list of Collection Tag ids to restrict search to. Defaults to all Collection Tags by passing an empty list + /// A list of Collection Tag ids to restrict search to. Defaults to all genres by passing an empty list /// public IList CollectionTags { get; init; } = new List(); /// - /// A list of Tag ids to restrict search to. Defaults to all Tags by passing an empty list + /// A list of Tag ids to restrict search to. Defaults to all genres by passing an empty list /// public IList Tags { get; init; } = new List(); /// diff --git a/API/DTOs/Reader/ChapterInfoDto.cs b/API/DTOs/Reader/ChapterInfoDto.cs index 9f33bada7..7c847d926 100644 --- a/API/DTOs/Reader/ChapterInfoDto.cs +++ b/API/DTOs/Reader/ChapterInfoDto.cs @@ -12,7 +12,6 @@ namespace API.DTOs.Reader public MangaFormat SeriesFormat { get; set; } public int SeriesId { get; set; } public int LibraryId { get; set; } - public LibraryType LibraryType { get; set; } public string ChapterTitle { get; set; } = string.Empty; public int Pages { get; set; } public string FileName { get; set; } diff --git a/API/Data/Repositories/ChapterRepository.cs b/API/Data/Repositories/ChapterRepository.cs index ab3684fa0..330aa4b5e 100644 --- a/API/Data/Repositories/ChapterRepository.cs +++ b/API/Data/Repositories/ChapterRepository.cs @@ -81,8 +81,7 @@ public class ChapterRepository : IChapterRepository data.TitleName, SeriesFormat = series.Format, SeriesName = series.Name, - series.LibraryId, - LibraryType = series.Library.Type + series.LibraryId }) .Select(data => new ChapterInfoDto() { @@ -90,13 +89,12 @@ public class ChapterRepository : IChapterRepository VolumeNumber = data.VolumeNumber + string.Empty, VolumeId = data.VolumeId, IsSpecial = data.IsSpecial, - SeriesId = data.SeriesId, + SeriesId =data.SeriesId, SeriesFormat = data.SeriesFormat, SeriesName = data.SeriesName, LibraryId = data.LibraryId, Pages = data.Pages, - ChapterTitle = data.TitleName, - LibraryType = data.LibraryType + ChapterTitle = data.TitleName }) .AsNoTracking() .AsSplitQuery() diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index d70681b46..efe2f1a27 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -79,8 +79,8 @@ public interface ISeriesRepository /// Task AddSeriesModifiers(int userId, List series); Task GetSeriesCoverImageAsync(int seriesId); - Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto filter, bool cutoffOnDate = true); - Task> GetRecentlyAdded(int libraryId, int userId, UserParams userParams, FilterDto filter); + Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto filter); + Task> GetRecentlyAdded(int libraryId, int userId, UserParams userParams, FilterDto filter); // NOTE: Probably put this in LibraryRepo Task GetSeriesMetadata(int seriesId); Task> GetSeriesDtoForCollectionAsync(int collectionId, int userId, UserParams userParams); Task> GetFilesForSeries(int seriesId); @@ -593,11 +593,11 @@ public class SeriesRepository : ISeriesRepository /// Pagination information /// Optional (default null) filter on query /// - public async Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto filter, bool cutoffOnDate = true) + public async Task> GetOnDeck(int userId, int libraryId, UserParams userParams, FilterDto filter) { //var allSeriesWithProgress = await _context.AppUserProgresses.Select(p => p.SeriesId).ToListAsync(); //var allChapters = await GetChapterIdsForSeriesAsync(allSeriesWithProgress); - var cutoffProgressPoint = DateTime.Now - TimeSpan.FromDays(30); + var cuttoffProgressPoint = DateTime.Now - TimeSpan.FromDays(30); var query = (await CreateFilteredSearchQueryable(userId, libraryId, filter)) .Join(_context.AppUserProgresses, s => s.Id, progress => progress.SeriesId, (s, progress) => new @@ -612,12 +612,8 @@ public class SeriesRepository : ISeriesRepository // 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) - }); - if (cutoffOnDate) - { - query = query.Where(d => d.LastReadingProgress >= cutoffProgressPoint); - } - + }) + .Where(d => d.LastReadingProgress >= cuttoffProgressPoint); // I think I need another Join statement. The problem is the chapters are still limited to progress diff --git a/API/Services/EmailService.cs b/API/Services/EmailService.cs index c5ba90464..ab2c52a2c 100644 --- a/API/Services/EmailService.cs +++ b/API/Services/EmailService.cs @@ -98,7 +98,7 @@ public class EmailService : IEmailService return await SendEmailWithPost(emailLink + "/api/email/email-password-reset", data); } - private static async Task SendEmailWithGet(string url, int timeoutSecs = 30) + private static async Task SendEmailWithGet(string url) { try { @@ -108,7 +108,7 @@ public class EmailService : IEmailService .WithHeader("x-api-key", "MsnvA2DfQqxSK5jh") .WithHeader("x-kavita-version", BuildInfo.Version) .WithHeader("Content-Type", "application/json") - .WithTimeout(TimeSpan.FromSeconds(timeoutSecs)) + .WithTimeout(TimeSpan.FromSeconds(30)) .GetStringAsync(); if (!string.IsNullOrEmpty(response) && bool.Parse(response)) @@ -124,7 +124,7 @@ public class EmailService : IEmailService } - private static async Task SendEmailWithPost(string url, object data, int timeoutSecs = 30) + private static async Task SendEmailWithPost(string url, object data) { try { @@ -134,7 +134,7 @@ public class EmailService : IEmailService .WithHeader("x-api-key", "MsnvA2DfQqxSK5jh") .WithHeader("x-kavita-version", BuildInfo.Version) .WithHeader("Content-Type", "application/json") - .WithTimeout(TimeSpan.FromSeconds(timeoutSecs)) + .WithTimeout(TimeSpan.FromSeconds(30)) .PostJsonAsync(data); if (response.StatusCode != StatusCodes.Status200OK) diff --git a/API/Services/ReaderService.cs b/API/Services/ReaderService.cs index ab78437c2..ee8647187 100644 --- a/API/Services/ReaderService.cs +++ b/API/Services/ReaderService.cs @@ -16,8 +16,6 @@ namespace API.Services; public interface IReaderService { - Task MarkSeriesAsRead(AppUser user, int seriesId); - Task MarkSeriesAsUnread(AppUser user, int seriesId); void MarkChaptersAsRead(AppUser user, int seriesId, IEnumerable chapters); void MarkChaptersAsUnread(AppUser user, int seriesId, IEnumerable chapters); Task SaveReadingProgress(ProgressDto progressDto, int userId); @@ -47,40 +45,6 @@ public class ReaderService : IReaderService return Parser.Parser.NormalizePath(Path.Join(baseDirectory, $"{userId}", $"{seriesId}", $"{chapterId}")); } - /// - /// Does not commit. Marks all entities under the series as read. - /// - /// - /// - public async Task MarkSeriesAsRead(AppUser user, int seriesId) - { - var volumes = await _unitOfWork.VolumeRepository.GetVolumes(seriesId); - user.Progresses ??= new List(); - foreach (var volume in volumes) - { - MarkChaptersAsRead(user, seriesId, volume.Chapters); - } - - _unitOfWork.UserRepository.Update(user); - } - - /// - /// Does not commit. Marks all entities under the series as unread. - /// - /// - /// - public async Task MarkSeriesAsUnread(AppUser user, int seriesId) - { - var volumes = await _unitOfWork.VolumeRepository.GetVolumes(seriesId); - user.Progresses ??= new List(); - foreach (var volume in volumes) - { - MarkChaptersAsUnread(user, seriesId, volume.Chapters); - } - - _unitOfWork.UserRepository.Update(user); - } - /// /// Marks all Chapters as Read by creating or updating UserProgress rows. Does not commit. /// @@ -403,7 +367,7 @@ public class ReaderService : IReaderService .ToList(); // If there are any volumes that have progress, return those. If not, move on. - var currentlyReadingChapter = volumeChapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages); // (removed for GetContinuePoint_ShouldReturnFirstVolumeChapter_WhenPreExistingProgress), not sure if needed && chapter.PagesRead > 0 + var currentlyReadingChapter = volumeChapters.FirstOrDefault(chapter => chapter.PagesRead < chapter.Pages && chapter.PagesRead > 0); if (currentlyReadingChapter != null) return currentlyReadingChapter; // Check loose leaf chapters (and specials). First check if there are any diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 381e53452..7ee020cd9 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/invite-user-response.ts b/UI/Web/src/app/_models/invite-user-response.ts deleted file mode 100644 index a9042c555..000000000 --- a/UI/Web/src/app/_models/invite-user-response.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface InviteUserResponse { - /** - * Link to register new user - */ - emailLink: string; - /** - * If an email was sent to the invited user - */ - emailSent: boolean; -} \ No newline at end of file diff --git a/UI/Web/src/app/_services/account.service.ts b/UI/Web/src/app/_services/account.service.ts index df5555f41..419dec77d 100644 --- a/UI/Web/src/app/_services/account.service.ts +++ b/UI/Web/src/app/_services/account.service.ts @@ -8,7 +8,6 @@ import { User } from '../_models/user'; import { Router } from '@angular/router'; import { MessageHubService } from './message-hub.service'; import { ThemeService } from '../theme.service'; -import { InviteUserResponse } from '../_models/invite-user-response'; @Injectable({ providedIn: 'root' @@ -22,9 +21,6 @@ export class AccountService implements OnDestroy { // Stores values, when someone subscribes gives (1) of last values seen. private currentUserSource = new ReplaySubject(1); - /** - * - */ currentUser$ = this.currentUserSource.asObservable(); /** @@ -63,7 +59,6 @@ export class AccountService implements OnDestroy { map((response: User) => { const user = response; if (user) { - console.log('Login: ', user); this.setCurrentUser(user); this.messageHub.createHubConnection(user, this.hasAdminRole(user)); } @@ -135,8 +130,8 @@ export class AccountService implements OnDestroy { return this.httpClient.post(this.baseUrl + 'account/resend-confirmation-email?userId=' + userId, {}, {responseType: 'text' as 'json'}); } - inviteUser(model: {email: string, roles: Array, libraries: Array}) { - return this.httpClient.post(this.baseUrl + 'account/invite', model); + inviteUser(model: {email: string, roles: Array, libraries: Array, sendEmail: boolean}) { + return this.httpClient.post(this.baseUrl + 'account/invite', model, {responseType: 'text' as 'json'}); } confirmEmail(model: {email: string, username: string, password: string, token: string}) { diff --git a/UI/Web/src/app/_services/nav.service.ts b/UI/Web/src/app/_services/nav.service.ts index c1ca3f0ad..9dba9d9b0 100644 --- a/UI/Web/src/app/_services/nav.service.ts +++ b/UI/Web/src/app/_services/nav.service.ts @@ -1,23 +1,71 @@ import { Injectable } from '@angular/core'; -import { ReplaySubject } from 'rxjs'; +import { ReplaySubject, take } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class NavService { + public localStorageSideNavKey = 'kavita--sidenav--collapsed'; private navbarVisibleSource = new ReplaySubject(1); + /** + * If the top Nav bar is rendered or not + */ navbarVisible$ = this.navbarVisibleSource.asObservable(); + private sideNavCollapseSource = new ReplaySubject(1); + /** + * If the Side Nav is in a collapsed state or not. + */ + sideNavCollapsed$ = this.sideNavCollapseSource.asObservable(); + + private sideNavVisibilitySource = new ReplaySubject(1); + /** + * If the side nav is rendered or not into the DOM. + */ + sideNavVisibility$ = this.sideNavVisibilitySource.asObservable(); + constructor() { this.showNavBar(); + const sideNavState = (localStorage.getItem(this.localStorageSideNavKey) === 'true') || false; + this.sideNavCollapseSource.next(sideNavState); + this.showSideNav(); } - + + /** + * Shows the top nav bar. This should be visible on all pages except the reader. + */ showNavBar() { this.navbarVisibleSource.next(true); } + /** + * Hides the top nav bar. + */ hideNavBar() { this.navbarVisibleSource.next(false); } + + /** + * Shows the side nav. When being visible, the side nav will automatically return to previous collapsed state. + */ + showSideNav() { + this.sideNavVisibilitySource.next(true); + } + + /** + * Hides the side nav. This is useful for the readers and login page. + */ + hideSideNav() { + this.sideNavVisibilitySource.next(false); + } + + toggleSideNav() { + this.sideNavCollapseSource.pipe(take(1)).subscribe(val => { + if (val === undefined) val = false; + const newVal = !(val || false); + this.sideNavCollapseSource.next(newVal); + localStorage.setItem(this.localStorageSideNavKey, newVal + ''); + }); + } } diff --git a/UI/Web/src/app/admin/admin.module.ts b/UI/Web/src/app/admin/admin.module.ts index d594cbff3..0f042a097 100644 --- a/UI/Web/src/app/admin/admin.module.ts +++ b/UI/Web/src/app/admin/admin.module.ts @@ -13,13 +13,13 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ResetPasswordModalComponent } from './_modals/reset-password-modal/reset-password-modal.component'; import { ManageSettingsComponent } from './manage-settings/manage-settings.component'; import { ManageSystemComponent } from './manage-system/manage-system.component'; -import { ChangelogComponent } from './changelog/changelog.component'; +import { ChangelogComponent } from '../announcements/changelog/changelog.component'; import { PipeModule } from '../pipe/pipe.module'; import { InviteUserComponent } from './invite-user/invite-user.component'; import { RoleSelectorComponent } from './role-selector/role-selector.component'; import { LibrarySelectorComponent } from './library-selector/library-selector.component'; import { EditUserComponent } from './edit-user/edit-user.component'; -import { UserSettingsModule } from '../user-settings/user-settings.module'; +import { SidenavModule } from '../sidenav/sidenav.module'; @@ -35,7 +35,6 @@ import { UserSettingsModule } from '../user-settings/user-settings.module'; ResetPasswordModalComponent, ManageSettingsComponent, ManageSystemComponent, - ChangelogComponent, InviteUserComponent, RoleSelectorComponent, LibrarySelectorComponent, @@ -51,7 +50,7 @@ import { UserSettingsModule } from '../user-settings/user-settings.module'; NgbDropdownModule, SharedModule, PipeModule, - UserSettingsModule // API-key componet + SidenavModule ], providers: [] }) diff --git a/UI/Web/src/app/admin/dashboard/dashboard.component.html b/UI/Web/src/app/admin/dashboard/dashboard.component.html index 84a81d506..7db5206ea 100644 --- a/UI/Web/src/app/admin/dashboard/dashboard.component.html +++ b/UI/Web/src/app/admin/dashboard/dashboard.component.html @@ -1,6 +1,9 @@ -
-

Admin Dashboard

- + +

+ Admin Dashboard +

+
+
diff --git a/UI/Web/src/app/admin/dashboard/dashboard.component.ts b/UI/Web/src/app/admin/dashboard/dashboard.component.ts index 33549db13..d84b3ba26 100644 --- a/UI/Web/src/app/admin/dashboard/dashboard.component.ts +++ b/UI/Web/src/app/admin/dashboard/dashboard.component.ts @@ -3,6 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { ToastrService } from 'ngx-toastr'; import { ServerService } from 'src/app/_services/server.service'; import { Title } from '@angular/platform-browser'; +import { NavService } from '../../_services/nav.service'; @@ -18,13 +19,12 @@ export class DashboardComponent implements OnInit { {title: 'Users', fragment: 'users'}, {title: 'Libraries', fragment: 'libraries'}, {title: 'System', fragment: 'system'}, - {title: 'Changelog', fragment: 'changelog'}, ]; counter = this.tabs.length + 1; active = this.tabs[0]; constructor(public route: ActivatedRoute, private serverService: ServerService, - private toastr: ToastrService, private titleService: Title) { + private toastr: ToastrService, private titleService: Title, public navService: NavService) { this.route.fragment.subscribe(frag => { const tab = this.tabs.filter(item => item.fragment === frag); if (tab.length > 0) { diff --git a/UI/Web/src/app/admin/invite-user/invite-user.component.html b/UI/Web/src/app/admin/invite-user/invite-user.component.html index 495b3f6e9..83fbb86eb 100644 --- a/UI/Web/src/app/admin/invite-user/invite-user.component.html +++ b/UI/Web/src/app/admin/invite-user/invite-user.component.html @@ -9,7 +9,13 @@ Invite a user to your server. Enter their email in and we will send them an email to create an account.

-
+

+ +  Checking accessibility of server... +

+ + +
@@ -22,6 +28,11 @@
+ +

Use this link to finish setting up the user account due to your server not being accessible outside your local network.

+ +
+
@@ -33,21 +44,12 @@
- -

User invited

-

You can use the following link below to setup the account for your user or use the copy button. You may need to log out before using the link to register a new user. - If your server is externallyaccessible, an email will have been sent to the user and the links can be used by them to finish setting up their account. -

- - -
-