Make reading profile buttons more user-friendly
Updating a profile no longer deletes all implicit profiles
This commit is contained in:
parent
45a44480e1
commit
82f557490a
9 changed files with 96 additions and 87 deletions
|
|
@ -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<UserReadingProfileDto>(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<UserReadingProfileDto>(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<UserReadingProfileDto>(profile);
|
||||
var newDto = Mapper.Map<UserReadingProfileDto>(profile);
|
||||
|
||||
Assert.True(RandfHelper.AreSimpleFieldsEqual(dto, newDto,
|
||||
["<Id>k__BackingField", "<AppUserId>k__BackingField", "<Implicit>k__BackingField"]));
|
||||
Assert.True(RandfHelper.AreSimpleFieldsEqual(dto, newDto,
|
||||
["<Id>k__BackingField", "<UserId>k__BackingField"]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -35,11 +35,12 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
/// Series -> Library -> Default
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="skipImplicit"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{seriesId}")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> GetProfileForSeries(int seriesId)
|
||||
[HttpGet("{seriesId:int}")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> GetProfileForSeries(int seriesId, [FromQuery] bool skipImplicit)
|
||||
{
|
||||
return Ok(await readingProfileService.GetReadingProfileDtoForSeries(User.GetUserId(), seriesId));
|
||||
return Ok(await readingProfileService.GetReadingProfileDtoForSeries(User.GetUserId(), seriesId, skipImplicit));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -126,7 +127,7 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
/// <param name="seriesId"></param>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("series/{seriesId}")]
|
||||
[HttpPost("series/{seriesId:int}")]
|
||||
public async Task<IActionResult> AddProfileToSeries(int seriesId, [FromQuery] int profileId)
|
||||
{
|
||||
await readingProfileService.AddProfileToSeries(User.GetUserId(), profileId, seriesId);
|
||||
|
|
@ -138,7 +139,7 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("series/{seriesId}")]
|
||||
[HttpDelete("series/{seriesId:int}")]
|
||||
public async Task<IActionResult> ClearSeriesProfile(int seriesId)
|
||||
{
|
||||
await readingProfileService.ClearSeriesProfile(User.GetUserId(), seriesId);
|
||||
|
|
@ -151,7 +152,7 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
/// <param name="libraryId"></param>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("library/{libraryId}")]
|
||||
[HttpPost("library/{libraryId:int}")]
|
||||
public async Task<IActionResult> AddProfileToLibrary(int libraryId, [FromQuery] int profileId)
|
||||
{
|
||||
await readingProfileService.AddProfileToLibrary(User.GetUserId(), profileId, libraryId);
|
||||
|
|
@ -164,7 +165,7 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
|||
/// <param name="libraryId"></param>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("library/{libraryId}")]
|
||||
[HttpDelete("library/{libraryId:int}")]
|
||||
public async Task<IActionResult> ClearLibraryProfile(int libraryId)
|
||||
{
|
||||
await readingProfileService.ClearLibraryProfile(User.GetUserId(), libraryId);
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ public interface IReadingProfileService
|
|||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="skipImplicit"></param>
|
||||
/// <returns></returns>
|
||||
Task<UserReadingProfileDto> GetReadingProfileDtoForSeries(int userId, int seriesId);
|
||||
Task<UserReadingProfileDto> GetReadingProfileDtoForSeries(int userId, int seriesId, bool skipImplicit = false);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new reading profile for a user. Name must be unique per user
|
||||
|
|
@ -60,7 +61,7 @@ public interface IReadingProfileService
|
|||
Task<UserReadingProfileDto> UpdateParent(int userId, int seriesId, UserReadingProfileDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// Updates a given reading profile for a user, and deletes all implicit profiles
|
||||
/// Updates a given reading profile for a user
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="dto"></param>
|
||||
|
|
@ -123,9 +124,9 @@ public interface IReadingProfileService
|
|||
public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService localizationService, IMapper mapper): IReadingProfileService
|
||||
{
|
||||
|
||||
public async Task<UserReadingProfileDto> GetReadingProfileDtoForSeries(int userId, int seriesId)
|
||||
public async Task<UserReadingProfileDto> GetReadingProfileDtoForSeries(int userId, int seriesId, bool skipImplicit = false)
|
||||
{
|
||||
return mapper.Map<UserReadingProfileDto>(await GetReadingProfileForSeries(userId, seriesId));
|
||||
return mapper.Map<UserReadingProfileDto>(await GetReadingProfileForSeries(userId, seriesId, skipImplicit));
|
||||
}
|
||||
|
||||
public async Task<AppUserReadingProfile> 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<UserReadingProfileDto>(profile);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export class ReadingProfileService {
|
|||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
getForSeries(seriesId: number) {
|
||||
return this.httpClient.get<ReadingProfile>(this.baseUrl + "ReadingProfile/"+seriesId);
|
||||
getForSeries(seriesId: number, skipImplicit: boolean = false) {
|
||||
return this.httpClient.get<ReadingProfile>(this.baseUrl + `ReadingProfile/${seriesId}?skipImplicit=${skipImplicit}`);
|
||||
}
|
||||
|
||||
updateProfile(profile: ReadingProfile) {
|
||||
|
|
|
|||
|
|
@ -170,9 +170,9 @@
|
|||
|
||||
<div class="d-flex gap-2 mt-2 flex-wrap mb-2">
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="readingProfile.kind !== ReadingProfileKind.Implicit"
|
||||
[disabled]="readingProfile.kind !== ReadingProfileKind.Implicit || !parentReadingProfile"
|
||||
(click)="updateParentPref()">
|
||||
{{ t('update-parent') }}
|
||||
{{ t('update-parent', {name: parentReadingProfile?.name || t('loading')}) }}
|
||||
</button>
|
||||
<button class="btn btn-primary"
|
||||
[ngbTooltip]="t('create-new-tooltip')"
|
||||
|
|
|
|||
|
|
@ -165,6 +165,11 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
|
||||
settingsForm: FormGroup = new FormGroup({});
|
||||
|
||||
/**
|
||||
* The reading profile itself, unless readingProfile is implicit
|
||||
*/
|
||||
parentReadingProfile: ReadingProfile | null = null;
|
||||
|
||||
/**
|
||||
* System provided themes
|
||||
*/
|
||||
|
|
@ -190,6 +195,14 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
private toastr: ToastrService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
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.fontFamilies = this.bookService.getFontFamilies();
|
||||
this.fontOptions = this.fontFamilies.map(f => f.title);
|
||||
|
|
@ -325,6 +338,10 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
|
||||
updateImplicit() {
|
||||
this.readingProfileService.updateImplicit(this.packReadingProfile(), this.seriesId).subscribe({
|
||||
next: newProfile => {
|
||||
this.readingProfile = newProfile;
|
||||
this.cdRef.markForCheck();
|
||||
},
|
||||
error: err => {
|
||||
console.error(err);
|
||||
}
|
||||
|
|
@ -414,6 +431,7 @@ export class ReaderSettingsComponent implements OnInit {
|
|||
|
||||
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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -310,8 +310,17 @@
|
|||
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<button class="btn btn-primary" [disabled]="readingProfile.kind !== ReadingProfileKind.Implicit" (click)="updateParentPref()">{{t('update-parent')}}</button>
|
||||
<button class="btn btn-primary ms-2" [ngbTooltip]="t('create-new-tooltip')" [disabled]="readingProfile.kind !== ReadingProfileKind.Implicit" (click)="createNewProfileFromImplicit()">{{t('create-new')}}</button>
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="readingProfile.kind !== ReadingProfileKind.Implicit || !parentReadingProfile"
|
||||
(click)="updateParentPref()">
|
||||
{{ t('update-parent', {name: parentReadingProfile?.name || t('loading')}) }}
|
||||
</button>
|
||||
<button class="btn btn-primary ms-2"
|
||||
[ngbTooltip]="t('create-new-tooltip')"
|
||||
[disabled]="readingProfile.kind !== ReadingProfileKind.Implicit"
|
||||
(click)="createNewProfileFromImplicit()">
|
||||
{{ t('create-new') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue