From 82f557490ad6aebb786d2b13c2fad89b184ada32 Mon Sep 17 00:00:00 2001 From: Amelia <77553571+Fesaa@users.noreply.github.com> Date: Fri, 6 Jun 2025 13:24:25 +0200 Subject: [PATCH] Make reading profile buttons more user-friendly Updating a profile no longer deletes all implicit profiles --- .../Services/ReadingProfileServiceTest.cs | 69 +++++-------------- API/Controllers/ReadingProfileController.cs | 15 ++-- API/Services/ReadingProfileService.cs | 11 +-- .../app/_services/reading-profile.service.ts | 4 +- .../reader-settings.component.html | 4 +- .../reader-settings.component.ts | 18 +++++ .../manga-reader/manga-reader.component.html | 13 +++- .../manga-reader/manga-reader.component.ts | 37 +++++++--- UI/Web/src/assets/langs/en.json | 12 ++-- 9 files changed, 96 insertions(+), 87 deletions(-) diff --git a/API.Tests/Services/ReadingProfileServiceTest.cs b/API.Tests/Services/ReadingProfileServiceTest.cs index 7fa998eb0..b3d81e5ac 100644 --- a/API.Tests/Services/ReadingProfileServiceTest.cs +++ b/API.Tests/Services/ReadingProfileServiceTest.cs @@ -67,13 +67,8 @@ public class ReadingProfileServiceTest: AbstractDbTest Assert.NotNull(seriesProfile); Assert.Equal("Implicit Profile", seriesProfile.Name); - await rps.UpdateReadingProfile(user.Id, new UserReadingProfileDto - { - Id = profile2.Id, - WidthOverride = 23, - }); - - seriesProfile = await rps.GetReadingProfileDtoForSeries(user.Id, series.Id); + // Find parent + seriesProfile = await rps.GetReadingProfileDtoForSeries(user.Id, series.Id, true); Assert.NotNull(seriesProfile); Assert.Equal("Non-implicit Profile", seriesProfile.Name); } @@ -260,7 +255,7 @@ public class ReadingProfileServiceTest: AbstractDbTest } [Fact] - public async Task BatchAddReadingProfiles() + public async Task BulkAddReadingProfiles() { await ResetDb(); var (rps, user, lib, series) = await Setup(); @@ -309,43 +304,7 @@ public class ReadingProfileServiceTest: AbstractDbTest } [Fact] - public async Task UpdateDeletesImplicit() - { - await ResetDb(); - var (rps, user, lib, series) = await Setup(); - - var implicitProfile = Mapper.Map(new AppUserReadingProfileBuilder(user.Id) - .Build()); - - var profile = new AppUserReadingProfileBuilder(user.Id) - .WithName("Profile 1") - .Build(); - Context.AppUserReadingProfiles.Add(profile); - await UnitOfWork.CommitAsync(); - - await rps.AddProfileToSeries(user.Id, profile.Id, series.Id); - await rps.UpdateImplicitReadingProfile(user.Id, series.Id, implicitProfile); - - - var seriesProfile = await rps.GetReadingProfileDtoForSeries(user.Id, series.Id); - Assert.NotNull(seriesProfile); - Assert.Equal(ReadingProfileKind.Implicit, seriesProfile.Kind); - - var profileDto = Mapper.Map(profile); - await rps.UpdateReadingProfile(user.Id, profileDto); - - seriesProfile = await rps.GetReadingProfileDtoForSeries(user.Id, series.Id); - Assert.NotNull(seriesProfile); - Assert.Equal(ReadingProfileKind.User, seriesProfile.Kind); - - var implicitCount = await Context.AppUserReadingProfiles - .Where(p => p.Kind == ReadingProfileKind.Implicit) - .CountAsync(); - Assert.Equal(0, implicitCount); - } - - [Fact] - public async Task BatchUpdateDeletesImplicit() + public async Task BulkAssignDeletesImplicit() { await ResetDb(); var (rps, user, lib, series) = await Setup(); @@ -574,18 +533,22 @@ public class ReadingProfileServiceTest: AbstractDbTest [Fact] public void UpdateFields_UpdatesAll() { - var profile = new AppUserReadingProfile(); - var dto = new UserReadingProfileDto(); + // Repeat to ensure booleans are flipped and actually tested + for (int i = 0; i < 10; i++) + { + var profile = new AppUserReadingProfile(); + var dto = new UserReadingProfileDto(); - RandfHelper.SetRandomValues(profile); - RandfHelper.SetRandomValues(dto); + RandfHelper.SetRandomValues(profile); + RandfHelper.SetRandomValues(dto); - ReadingProfileService.UpdateReaderProfileFields(profile, dto); + ReadingProfileService.UpdateReaderProfileFields(profile, dto); - var newDto = Mapper.Map(profile); + var newDto = Mapper.Map(profile); - Assert.True(RandfHelper.AreSimpleFieldsEqual(dto, newDto, - ["k__BackingField", "k__BackingField", "k__BackingField"])); + Assert.True(RandfHelper.AreSimpleFieldsEqual(dto, newDto, + ["k__BackingField", "k__BackingField"])); + } } diff --git a/API/Controllers/ReadingProfileController.cs b/API/Controllers/ReadingProfileController.cs index 596a00fef..214a267ea 100644 --- a/API/Controllers/ReadingProfileController.cs +++ b/API/Controllers/ReadingProfileController.cs @@ -35,11 +35,12 @@ public class ReadingProfileController(ILogger logger, /// Series -> Library -> Default /// /// + /// /// - [HttpGet("{seriesId}")] - public async Task> GetProfileForSeries(int seriesId) + [HttpGet("{seriesId:int}")] + public async Task> GetProfileForSeries(int seriesId, [FromQuery] bool skipImplicit) { - return Ok(await readingProfileService.GetReadingProfileDtoForSeries(User.GetUserId(), seriesId)); + return Ok(await readingProfileService.GetReadingProfileDtoForSeries(User.GetUserId(), seriesId, skipImplicit)); } /// @@ -126,7 +127,7 @@ public class ReadingProfileController(ILogger logger, /// /// /// - [HttpPost("series/{seriesId}")] + [HttpPost("series/{seriesId:int}")] public async Task AddProfileToSeries(int seriesId, [FromQuery] int profileId) { await readingProfileService.AddProfileToSeries(User.GetUserId(), profileId, seriesId); @@ -138,7 +139,7 @@ public class ReadingProfileController(ILogger logger, /// /// /// - [HttpDelete("series/{seriesId}")] + [HttpDelete("series/{seriesId:int}")] public async Task ClearSeriesProfile(int seriesId) { await readingProfileService.ClearSeriesProfile(User.GetUserId(), seriesId); @@ -151,7 +152,7 @@ public class ReadingProfileController(ILogger logger, /// /// /// - [HttpPost("library/{libraryId}")] + [HttpPost("library/{libraryId:int}")] public async Task AddProfileToLibrary(int libraryId, [FromQuery] int profileId) { await readingProfileService.AddProfileToLibrary(User.GetUserId(), profileId, libraryId); @@ -164,7 +165,7 @@ public class ReadingProfileController(ILogger logger, /// /// /// - [HttpDelete("library/{libraryId}")] + [HttpDelete("library/{libraryId:int}")] public async Task ClearLibraryProfile(int libraryId) { await readingProfileService.ClearLibraryProfile(User.GetUserId(), libraryId); diff --git a/API/Services/ReadingProfileService.cs b/API/Services/ReadingProfileService.cs index 46d7a6709..38cc5ae15 100644 --- a/API/Services/ReadingProfileService.cs +++ b/API/Services/ReadingProfileService.cs @@ -22,8 +22,9 @@ public interface IReadingProfileService /// /// /// + /// /// - Task GetReadingProfileDtoForSeries(int userId, int seriesId); + Task GetReadingProfileDtoForSeries(int userId, int seriesId, bool skipImplicit = false); /// /// Creates a new reading profile for a user. Name must be unique per user @@ -60,7 +61,7 @@ public interface IReadingProfileService Task UpdateParent(int userId, int seriesId, UserReadingProfileDto dto); /// - /// Updates a given reading profile for a user, and deletes all implicit profiles + /// Updates a given reading profile for a user /// /// /// @@ -123,9 +124,9 @@ public interface IReadingProfileService public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService localizationService, IMapper mapper): IReadingProfileService { - public async Task GetReadingProfileDtoForSeries(int userId, int seriesId) + public async Task GetReadingProfileDtoForSeries(int userId, int seriesId, bool skipImplicit = false) { - return mapper.Map(await GetReadingProfileForSeries(userId, seriesId)); + return mapper.Map(await GetReadingProfileForSeries(userId, seriesId, skipImplicit)); } public async Task GetReadingProfileForSeries(int userId, int seriesId, bool skipImplicit = false) @@ -175,7 +176,7 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService UpdateReaderProfileFields(profile, dto); unitOfWork.AppUserReadingProfileRepository.Update(profile); - await DeleteImplicateReadingProfilesForSeries(userId, profile.SeriesIds); + // await DeleteImplicateReadingProfilesForSeries(userId, profile.SeriesIds); await unitOfWork.CommitAsync(); return mapper.Map(profile); diff --git a/UI/Web/src/app/_services/reading-profile.service.ts b/UI/Web/src/app/_services/reading-profile.service.ts index 68e7c1059..341e44a91 100644 --- a/UI/Web/src/app/_services/reading-profile.service.ts +++ b/UI/Web/src/app/_services/reading-profile.service.ts @@ -12,8 +12,8 @@ export class ReadingProfileService { constructor(private httpClient: HttpClient) { } - getForSeries(seriesId: number) { - return this.httpClient.get(this.baseUrl + "ReadingProfile/"+seriesId); + getForSeries(seriesId: number, skipImplicit: boolean = false) { + return this.httpClient.get(this.baseUrl + `ReadingProfile/${seriesId}?skipImplicit=${skipImplicit}`); } updateProfile(profile: ReadingProfile) { diff --git a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html index 44ad0dfdf..5535f1141 100644 --- a/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html +++ b/UI/Web/src/app/book-reader/_components/reader-settings/reader-settings.component.html @@ -170,9 +170,9 @@
- + +
diff --git a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts index 50e78184a..f2958148a 100644 --- a/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts +++ b/UI/Web/src/app/manga-reader/_components/manga-reader/manga-reader.component.ts @@ -205,6 +205,10 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { totalSeriesPagesRead = 0; user!: User; readingProfile!: ReadingProfile; + /** + * The reading profile itself, unless readingProfile is implicit + */ + parentReadingProfile: ReadingProfile | null = null; generalSettingsForm!: FormGroup; readingDirection = ReadingDirection.LeftToRight; @@ -491,17 +495,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { return; } - this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(data => { - this.readingProfile = data['readingProfile']; - if (this.readingProfile == null) { - this.router.navigateByUrl('/home'); - return; - } - this.setupReaderSettings(); - this.cdRef.markForCheck(); - }); - - this.getPageFn = this.getPage.bind(this); this.libraryId = parseInt(libraryId, 10); @@ -510,6 +503,17 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.incognitoMode = this.route.snapshot.queryParamMap.get('incognitoMode') === 'true'; this.bookmarkMode = this.route.snapshot.queryParamMap.get('bookmarkMode') === 'true'; + this.route.data.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(data => { + this.readingProfile = data['readingProfile']; + if (this.readingProfile == null) { + this.router.navigateByUrl('/home'); + return; + } + // Requires seriesId to be set + this.setupReaderSettings(); + this.cdRef.markForCheck(); + }); + const readingListId = this.route.snapshot.queryParamMap.get('readingListId'); if (readingListId != null) { this.readingListMode = true; @@ -644,6 +648,16 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { } setupReaderSettings() { + + if (this.readingProfile.kind === ReadingProfileKind.Implicit) { + this.readingProfileService.getForSeries(this.seriesId, true).subscribe(parent => { + this.parentReadingProfile = parent; + this.cdRef.markForCheck(); + }) + } else { + this.parentReadingProfile = this.readingProfile; + } + this.readingDirection = this.readingProfile.readingDirection; this.scalingOption = this.readingProfile.scalingOption; this.pageSplitOption = this.readingProfile.pageSplitOption; @@ -1805,6 +1819,7 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy { this.readingProfileService.promoteProfile(this.readingProfile.id).subscribe(newProfile => { this.readingProfile = newProfile; + this.parentReadingProfile = newProfile; // Profile is no longer implicit this.toastr.success(translate("manga-reader.reading-profile-promoted")); this.cdRef.markForCheck(); }); diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index ffe093372..fa61bf94d 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -1132,8 +1132,9 @@ "line-spacing-label": "{{manage-reading-profiles.line-height-book-label}}", "margin-label": "{{manage-reading-profiles.margin-book-label}}", "reset-to-defaults": "Reset to Defaults", - "update-parent": "Update parent profile", - "create-new": "Promote profile", + "update-parent": "Save to {{name}}", + "loading": "loading", + "create-new": "New profile from implicit", "create-new-tooltip": "Create a new manageable profile from your current implicit one", "reading-profile-updated": "Reading profile updated", "reading-profile-promoted": "Reading profile promoted", @@ -1952,9 +1953,10 @@ "manga-reader": { "back": "Back", - "update-parent": "Update parent profile", - "create-new": "Promote profile", - "create-new-tooltip": "Create a new manageable profile from your current implicit one", + "update-parent": "{{reader-settings.update-parent}}", + "loading": "{{reader-settings.loading}}", + "create-new": "{{reader-settings.create-new}}", + "create-new-tooltip": "{{reader-settings.create-new-tooltip}}", "incognito-alt": "Incognito mode is on. Toggle to turn off.", "incognito-title": "Incognito Mode:", "shortcuts-menu-alt": "Keyboard Shortcuts Modal",