Correctly remove implicit profiles when updating
- Add actions (library actions aren't working ?) - Auto update for implicit is going off too often
This commit is contained in:
parent
823121f335
commit
558a1d73f5
14 changed files with 242 additions and 73 deletions
|
|
@ -221,7 +221,7 @@ public class ReadingProfileServiceTest: AbstractDbTest
|
||||||
Context.AppUserReadingProfile.Add(profile1);
|
Context.AppUserReadingProfile.Add(profile1);
|
||||||
await UnitOfWork.CommitAsync();
|
await UnitOfWork.CommitAsync();
|
||||||
|
|
||||||
await rps.RemoveProfileFromSeries(user.Id, profile1.Id, series.Id);
|
await rps.ClearSeriesProfile(user.Id, series.Id);
|
||||||
var profile = await rps.GetReadingProfileForSeries(user.Id, series.Id);
|
var profile = await rps.GetReadingProfileForSeries(user.Id, series.Id);
|
||||||
Assert.Null(profile);
|
Assert.Null(profile);
|
||||||
|
|
||||||
|
|
@ -254,7 +254,7 @@ public class ReadingProfileServiceTest: AbstractDbTest
|
||||||
await UnitOfWork.CommitAsync();
|
await UnitOfWork.CommitAsync();
|
||||||
|
|
||||||
var someSeriesIds = lib.Series.Take(lib.Series.Count / 2).Select(s => s.Id).ToList();
|
var someSeriesIds = lib.Series.Take(lib.Series.Count / 2).Select(s => s.Id).ToList();
|
||||||
await rps.BatchAddProfileToSeries(user.Id, profile.Id, someSeriesIds);
|
await rps.BulkAddProfileToSeries(user.Id, profile.Id, someSeriesIds);
|
||||||
|
|
||||||
foreach (var id in someSeriesIds)
|
foreach (var id in someSeriesIds)
|
||||||
{
|
{
|
||||||
|
|
@ -264,7 +264,7 @@ public class ReadingProfileServiceTest: AbstractDbTest
|
||||||
}
|
}
|
||||||
|
|
||||||
var allIds = lib.Series.Select(s => s.Id).ToList();
|
var allIds = lib.Series.Select(s => s.Id).ToList();
|
||||||
await rps.BatchAddProfileToSeries(user.Id, profile2.Id, allIds);
|
await rps.BulkAddProfileToSeries(user.Id, profile2.Id, allIds);
|
||||||
|
|
||||||
foreach (var id in allIds)
|
foreach (var id in allIds)
|
||||||
{
|
{
|
||||||
|
|
@ -342,7 +342,7 @@ public class ReadingProfileServiceTest: AbstractDbTest
|
||||||
Assert.True(seriesProfile.Implicit);
|
Assert.True(seriesProfile.Implicit);
|
||||||
}
|
}
|
||||||
|
|
||||||
await rps.BatchAddProfileToSeries(user.Id, profile.Id, ids);
|
await rps.BulkAddProfileToSeries(user.Id, profile.Id, ids);
|
||||||
|
|
||||||
foreach (var id in ids)
|
foreach (var id in ids)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -127,15 +127,14 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the reading profile from a series for the current logged-in user
|
/// Clears the reading profile for the given series for the currently logged-in user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="seriesId"></param>
|
/// <param name="seriesId"></param>
|
||||||
/// <param name="profileId"></param>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpDelete("series/{seriesId}")]
|
[HttpDelete("series/{seriesId}")]
|
||||||
public async Task<IActionResult> DeleteProfileFromSeries(int seriesId, [FromQuery] int profileId)
|
public async Task<IActionResult> ClearSeriesProfile(int seriesId)
|
||||||
{
|
{
|
||||||
await readingProfileService.RemoveProfileFromSeries(User.GetUserId(), profileId, seriesId);
|
await readingProfileService.ClearSeriesProfile(User.GetUserId(), seriesId);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,15 +152,15 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove the reading profile from a library for the current logged-in user
|
/// Clears the reading profile for the given library for the currently logged-in user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="libraryId"></param>
|
/// <param name="libraryId"></param>
|
||||||
/// <param name="profileId"></param>
|
/// <param name="profileId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpDelete("library/{libraryId}")]
|
[HttpDelete("library/{libraryId}")]
|
||||||
public async Task<IActionResult> DeleteProfileFromLibrary(int libraryId, [FromQuery] int profileId)
|
public async Task<IActionResult> ClearLibraryProfile(int libraryId)
|
||||||
{
|
{
|
||||||
await readingProfileService.RemoveProfileFromLibrary(User.GetUserId(), profileId, libraryId);
|
await readingProfileService.ClearLibraryProfile(User.GetUserId(), libraryId);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,10 +170,10 @@ public class ReadingProfileController(ILogger<ReadingProfileController> logger,
|
||||||
/// <param name="profileId"></param>
|
/// <param name="profileId"></param>
|
||||||
/// <param name="seriesIds"></param>
|
/// <param name="seriesIds"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("batch")]
|
[HttpPost("bulk")]
|
||||||
public async Task<IActionResult> BatchAddReadingProfile([FromQuery] int profileId, [FromBody] IList<int> seriesIds)
|
public async Task<IActionResult> BulkAddReadingProfile([FromQuery] int profileId, [FromBody] IList<int> seriesIds)
|
||||||
{
|
{
|
||||||
await readingProfileService.BatchAddProfileToSeries(User.GetUserId(), profileId, seriesIds);
|
await readingProfileService.BulkAddProfileToSeries(User.GetUserId(), profileId, seriesIds);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,14 @@ public interface IAppUserReadingProfileRepository
|
||||||
Task<IList<AppUserReadingProfile>> GetProfilesForUser(int userId, bool nonImplicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
Task<IList<AppUserReadingProfile>> GetProfilesForUser(int userId, bool nonImplicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
||||||
Task<IList<UserReadingProfileDto>> GetProfilesDtoForUser(int userId, bool nonImplicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
Task<IList<UserReadingProfileDto>> GetProfilesDtoForUser(int userId, bool nonImplicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
||||||
Task<AppUserReadingProfile?> GetProfileForSeries(int userId, int seriesId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
Task<AppUserReadingProfile?> GetProfileForSeries(int userId, int seriesId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
||||||
|
/// <summary>
|
||||||
|
/// Returns both implicit and "real" reading profiles
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <param name="seriesId"></param>
|
||||||
|
/// <param name="includes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<IList<AppUserReadingProfile>> GetAllProfilesForSeries(int userId, int seriesId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
||||||
Task<IList<AppUserReadingProfile>> GetProfilesForSeries(int userId, IList<int> seriesIds, bool implicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
Task<IList<AppUserReadingProfile>> GetProfilesForSeries(int userId, IList<int> seriesIds, bool implicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
||||||
Task<UserReadingProfileDto?> GetProfileDtoForSeries(int userId, int seriesId);
|
Task<UserReadingProfileDto?> GetProfileDtoForSeries(int userId, int seriesId);
|
||||||
Task<AppUserReadingProfile?> GetProfileForLibrary(int userId, int libraryId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
Task<AppUserReadingProfile?> GetProfileForLibrary(int userId, int libraryId, ReadingProfileIncludes includes = ReadingProfileIncludes.None);
|
||||||
|
|
@ -76,6 +84,14 @@ public class AppUserReadingProfileRepository(DataContext context, IMapper mapper
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IList<AppUserReadingProfile>> GetAllProfilesForSeries(int userId, int seriesId, ReadingProfileIncludes includes = ReadingProfileIncludes.None)
|
||||||
|
{
|
||||||
|
return await context.AppUserReadingProfile
|
||||||
|
.Where(rp => rp.UserId == userId && rp.Series.Any(s => s.SeriesId == seriesId))
|
||||||
|
.Includes(includes)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IList<AppUserReadingProfile>> GetProfilesForSeries(int userId, IList<int> seriesIds, bool implicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None)
|
public async Task<IList<AppUserReadingProfile>> GetProfilesForSeries(int userId, IList<int> seriesIds, bool implicitOnly, ReadingProfileIncludes includes = ReadingProfileIncludes.None)
|
||||||
{
|
{
|
||||||
return await context.AppUserReadingProfile
|
return await context.AppUserReadingProfile
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,11 @@ public interface IReadingProfileService
|
||||||
Task SetDefaultReadingProfile(int userId, int profileId);
|
Task SetDefaultReadingProfile(int userId, int profileId);
|
||||||
|
|
||||||
Task AddProfileToSeries(int userId, int profileId, int seriesId);
|
Task AddProfileToSeries(int userId, int profileId, int seriesId);
|
||||||
Task BatchAddProfileToSeries(int userId, int profileId, IList<int> seriesIds);
|
Task BulkAddProfileToSeries(int userId, int profileId, IList<int> seriesIds);
|
||||||
Task RemoveProfileFromSeries(int userId, int profileId, int seriesId);
|
Task ClearSeriesProfile(int userId, int seriesId);
|
||||||
|
|
||||||
Task AddProfileToLibrary(int userId, int profileId, int libraryId);
|
Task AddProfileToLibrary(int userId, int profileId, int libraryId);
|
||||||
Task RemoveProfileFromLibrary(int userId, int profileId, int libraryId);
|
Task ClearLibraryProfile(int userId, int libraryId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,6 +200,10 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
||||||
|
|
||||||
if (profile.UserId != userId) throw new UnauthorizedAccessException();
|
if (profile.UserId != userId) throw new UnauthorizedAccessException();
|
||||||
|
|
||||||
|
// Remove all implicit profiles
|
||||||
|
var implicitProfiles = await unitOfWork.AppUserReadingProfileRepository.GetProfilesForSeries(userId, [seriesId], true);
|
||||||
|
unitOfWork.AppUserReadingProfileRepository.RemoveRange(implicitProfiles);
|
||||||
|
|
||||||
var seriesProfile = await unitOfWork.AppUserReadingProfileRepository.GetSeriesProfile(userId, seriesId);
|
var seriesProfile = await unitOfWork.AppUserReadingProfileRepository.GetSeriesProfile(userId, seriesId);
|
||||||
if (seriesProfile != null)
|
if (seriesProfile != null)
|
||||||
{
|
{
|
||||||
|
|
@ -219,7 +223,7 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BatchAddProfileToSeries(int userId, int profileId, IList<int> seriesIds)
|
public async Task BulkAddProfileToSeries(int userId, int profileId, IList<int> seriesIds)
|
||||||
{
|
{
|
||||||
var profile = await unitOfWork.AppUserReadingProfileRepository.GetProfile(profileId, ReadingProfileIncludes.Series);
|
var profile = await unitOfWork.AppUserReadingProfileRepository.GetProfile(profileId, ReadingProfileIncludes.Series);
|
||||||
if (profile == null) throw new KavitaException("profile-not-found");
|
if (profile == null) throw new KavitaException("profile-not-found");
|
||||||
|
|
@ -254,14 +258,23 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveProfileFromSeries(int userId, int profileId, int seriesId)
|
public async Task ClearSeriesProfile(int userId, int seriesId)
|
||||||
{
|
{
|
||||||
var profile = await unitOfWork.AppUserReadingProfileRepository.GetProfile(profileId);
|
var profiles = await unitOfWork.AppUserReadingProfileRepository.GetAllProfilesForSeries(userId, seriesId, ReadingProfileIncludes.Series);
|
||||||
if (profile == null) throw new KavitaException("profile-not-found");
|
if (!profiles.Any()) return;
|
||||||
|
|
||||||
if (profile.UserId != userId) throw new UnauthorizedAccessException();
|
foreach (var profile in profiles)
|
||||||
|
{
|
||||||
|
if (profile.Implicit)
|
||||||
|
{
|
||||||
|
unitOfWork.AppUserReadingProfileRepository.Remove(profile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
profile.Series = profile.Series.Where(s => !(s.SeriesId == seriesId && s.AppUserId == userId)).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
profile.Series = profile.Series.Where(s => s.SeriesId != seriesId).ToList();
|
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,14 +299,21 @@ public class ReadingProfileService(IUnitOfWork unitOfWork, ILocalizationService
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveProfileFromLibrary(int userId, int profileId, int libraryId)
|
public async Task ClearLibraryProfile(int userId, int libraryId)
|
||||||
{
|
{
|
||||||
var profile = await unitOfWork.AppUserReadingProfileRepository.GetProfile(profileId);
|
var profile = await unitOfWork.AppUserReadingProfileRepository.GetProfileForLibrary(userId, libraryId, ReadingProfileIncludes.Library);
|
||||||
if (profile == null) throw new KavitaException("profile-not-found");
|
if (profile == null) return;
|
||||||
|
|
||||||
if (profile.UserId != userId) throw new UnauthorizedAccessException();
|
if (profile.Implicit)
|
||||||
|
{
|
||||||
|
unitOfWork.AppUserReadingProfileRepository.Remove(profile);
|
||||||
|
await unitOfWork.CommitAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
profile.Libraries = profile.Libraries.Where(s => s.LibraryId != libraryId).ToList();
|
profile.Libraries = profile.Libraries
|
||||||
|
.Where(s => !(s.LibraryId == libraryId && s.AppUserId == userId))
|
||||||
|
.ToList();
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,10 @@ export enum Action {
|
||||||
* Add to a reading profile
|
* Add to a reading profile
|
||||||
*/
|
*/
|
||||||
SetReadingProfile = 30,
|
SetReadingProfile = 30,
|
||||||
|
/**
|
||||||
|
* Remove the reading profile from the entity
|
||||||
|
*/
|
||||||
|
ClearReadingProfile = 31,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -346,6 +350,37 @@ export class ActionFactoryService {
|
||||||
requiredRoles: [Role.Admin],
|
requiredRoles: [Role.Admin],
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: Action.Submenu,
|
||||||
|
title: 'reading-profiles',
|
||||||
|
description: '',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
shouldRender: this.dummyShouldRender,
|
||||||
|
requiresAdmin: false,
|
||||||
|
requiredRoles: [],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
action: Action.SetReadingProfile,
|
||||||
|
title: 'set-reading-profile',
|
||||||
|
description: 'set-reading-profile-tooltip',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
shouldRender: this.dummyShouldRender,
|
||||||
|
requiresAdmin: false,
|
||||||
|
requiredRoles: [],
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.ClearReadingProfile,
|
||||||
|
title: 'clear-reading-profile',
|
||||||
|
description: 'clear-reading-profile-tooltip',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
shouldRender: this.dummyShouldRender,
|
||||||
|
requiresAdmin: false,
|
||||||
|
requiredRoles: [],
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: Action.Submenu,
|
action: Action.Submenu,
|
||||||
title: 'others',
|
title: 'others',
|
||||||
|
|
@ -559,6 +594,37 @@ export class ActionFactoryService {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
action: Action.Submenu,
|
||||||
|
title: 'reading-profiles',
|
||||||
|
description: '',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
shouldRender: this.dummyShouldRender,
|
||||||
|
requiresAdmin: false,
|
||||||
|
requiredRoles: [],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
action: Action.SetReadingProfile,
|
||||||
|
title: 'set-reading-profile',
|
||||||
|
description: 'set-reading-profile-tooltip',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
shouldRender: this.dummyShouldRender,
|
||||||
|
requiresAdmin: false,
|
||||||
|
requiredRoles: [],
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Action.ClearReadingProfile,
|
||||||
|
title: 'clear-reading-profile',
|
||||||
|
description: 'clear-reading-profile-tooltip',
|
||||||
|
callback: this.dummyCallback,
|
||||||
|
shouldRender: this.dummyShouldRender,
|
||||||
|
requiresAdmin: false,
|
||||||
|
requiredRoles: [],
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
action: Action.Submenu,
|
action: Action.Submenu,
|
||||||
title: 'others',
|
title: 'others',
|
||||||
|
|
@ -598,16 +664,6 @@ export class ActionFactoryService {
|
||||||
requiredRoles: [Role.Admin],
|
requiredRoles: [Role.Admin],
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
action: Action.SetReadingProfile,
|
|
||||||
title: 'set-reading-profile',
|
|
||||||
description: 'set-reading-profile-tooltip',
|
|
||||||
callback: this.dummyCallback,
|
|
||||||
shouldRender: this.dummyShouldRender,
|
|
||||||
requiresAdmin: false,
|
|
||||||
requiredRoles: [],
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
action: Action.Delete,
|
action: Action.Delete,
|
||||||
title: 'delete',
|
title: 'delete',
|
||||||
|
|
|
||||||
|
|
@ -819,12 +819,38 @@ export class ActionService {
|
||||||
* @param series
|
* @param series
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
SetReadingProfileForMultiple(series: Array<Series>, callback?: BooleanActionCallback) {
|
setReadingProfileForMultiple(series: Array<Series>, callback?: BooleanActionCallback) {
|
||||||
if (this.readingListModalRef != null) { return; }
|
if (this.readingListModalRef != null) { return; }
|
||||||
|
|
||||||
this.readingListModalRef = this.modalService.open(BulkSetReadingProfileComponent, { scrollable: true, size: 'md', fullscreen: 'md' });
|
this.readingListModalRef = this.modalService.open(BulkSetReadingProfileComponent, { scrollable: true, size: 'md', fullscreen: 'md' });
|
||||||
this.readingListModalRef.componentInstance.seriesIds = series.map(s => s.id)
|
this.readingListModalRef.componentInstance.seriesIds = series.map(s => s.id)
|
||||||
this.readingListModalRef.componentInstance.title = "hi"
|
this.readingListModalRef.componentInstance.title = ""
|
||||||
|
|
||||||
|
this.readingListModalRef.closed.pipe(take(1)).subscribe(() => {
|
||||||
|
this.readingListModalRef = null;
|
||||||
|
if (callback) {
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.readingListModalRef.dismissed.pipe(take(1)).subscribe(() => {
|
||||||
|
this.readingListModalRef = null;
|
||||||
|
if (callback) {
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the reading profile for multiple series
|
||||||
|
* @param library
|
||||||
|
* @param callback
|
||||||
|
*/
|
||||||
|
setReadingProfileForLibrary(library: Library, callback?: BooleanActionCallback) {
|
||||||
|
if (this.readingListModalRef != null) { return; }
|
||||||
|
|
||||||
|
this.readingListModalRef = this.modalService.open(BulkSetReadingProfileComponent, { scrollable: true, size: 'md', fullscreen: 'md' });
|
||||||
|
this.readingListModalRef.componentInstance.libraryId = library.id;
|
||||||
|
this.readingListModalRef.componentInstance.title = ""
|
||||||
|
|
||||||
this.readingListModalRef.closed.pipe(take(1)).subscribe(() => {
|
this.readingListModalRef.closed.pipe(take(1)).subscribe(() => {
|
||||||
this.readingListModalRef = null;
|
this.readingListModalRef = null;
|
||||||
|
|
|
||||||
|
|
@ -44,20 +44,20 @@ export class ReadingProfileService {
|
||||||
return this.httpClient.post(this.baseUrl + `ReadingProfile/series/${seriesId}?profileId=${id}`, {});
|
return this.httpClient.post(this.baseUrl + `ReadingProfile/series/${seriesId}?profileId=${id}`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromSeries(id: number, seriesId: number) {
|
clearSeriesProfiles(seriesId: number) {
|
||||||
return this.httpClient.delete(this.baseUrl + `ReadingProfile/series/${seriesId}?profileId=${id}`, {});
|
return this.httpClient.delete(this.baseUrl + `ReadingProfile/series/${seriesId}`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
addToLibrary(id: number, libraryId: number) {
|
addToLibrary(id: number, libraryId: number) {
|
||||||
return this.httpClient.post(this.baseUrl + `ReadingProfile/library/${libraryId}?profileId=${id}`, {});
|
return this.httpClient.post(this.baseUrl + `ReadingProfile/library/${libraryId}?profileId=${id}`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromLibrary(id: number, libraryId: number) {
|
clearLibraryProfiles(libraryId: number) {
|
||||||
return this.httpClient.delete(this.baseUrl + `ReadingProfile/library/${libraryId}?profileId=${id}`, {});
|
return this.httpClient.delete(this.baseUrl + `ReadingProfile/library/${libraryId}`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
batchAddToSeries(id: number, seriesIds: number[]) {
|
bulkAddToSeries(id: number, seriesIds: number[]) {
|
||||||
return this.httpClient.post(this.baseUrl + `ReadingProfile/batch?profileId=${id}`, seriesIds);
|
return this.httpClient.post(this.baseUrl + `ReadingProfile/bulk?profileId=${id}`, seriesIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,10 @@ export class BulkSetReadingProfileComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
@Input({required: true}) title!: string;
|
@Input({required: true}) title!: string;
|
||||||
/**
|
/**
|
||||||
* Series Ids to add to Collection Tag
|
* Series Ids to add to Reading Profile
|
||||||
*/
|
*/
|
||||||
@Input() seriesIds: Array<number> = [];
|
@Input() seriesIds: Array<number> = [];
|
||||||
|
@Input() libraryId: number | undefined;
|
||||||
@ViewChild('title') inputElem!: ElementRef<HTMLInputElement>;
|
@ViewChild('title') inputElem!: ElementRef<HTMLInputElement>;
|
||||||
|
|
||||||
profiles: Array<ReadingProfile> = [];
|
profiles: Array<ReadingProfile> = [];
|
||||||
|
|
@ -63,12 +64,28 @@ export class BulkSetReadingProfileComponent implements OnInit, AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
addToProfile(profile: ReadingProfile) {
|
addToProfile(profile: ReadingProfile) {
|
||||||
if (this.seriesIds.length === 0) return;
|
if (this.seriesIds.length == 1) {
|
||||||
|
this.readingProfileService.addToSeries(profile.id, this.seriesIds[0]).subscribe(() => {
|
||||||
this.readingProfileService.batchAddToSeries(profile.id, this.seriesIds).subscribe(() => {
|
|
||||||
this.toastr.success(translate('toasts.series-added-to-reading-profile', {name: profile.name}));
|
this.toastr.success(translate('toasts.series-added-to-reading-profile', {name: profile.name}));
|
||||||
this.modal.close();
|
this.modal.close();
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.seriesIds.length > 1) {
|
||||||
|
this.readingProfileService.bulkAddToSeries(profile.id, this.seriesIds).subscribe(() => {
|
||||||
|
this.toastr.success(translate('toasts.series-added-to-reading-profile', {name: profile.name}));
|
||||||
|
this.modal.close();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.libraryId) {
|
||||||
|
this.readingProfileService.addToLibrary(profile.id, this.libraryId).subscribe(() => {
|
||||||
|
this.toastr.success(translate('toasts.library-added-to-reading-profile', {name: profile.name}));
|
||||||
|
this.modal.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filterList = (listItem: ReadingProfile) => {
|
filterList = (listItem: ReadingProfile) => {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import {RelationKind} from 'src/app/_models/series-detail/relation-kind';
|
||||||
import {DecimalPipe} from "@angular/common";
|
import {DecimalPipe} from "@angular/common";
|
||||||
import {RelationshipPipe} from "../../_pipes/relationship.pipe";
|
import {RelationshipPipe} from "../../_pipes/relationship.pipe";
|
||||||
import {Device} from "../../_models/device/device";
|
import {Device} from "../../_models/device/device";
|
||||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
|
||||||
import {SeriesPreviewDrawerComponent} from "../../_single-module/series-preview-drawer/series-preview-drawer.component";
|
import {SeriesPreviewDrawerComponent} from "../../_single-module/series-preview-drawer/series-preview-drawer.component";
|
||||||
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
import {CardActionablesComponent} from "../../_single-module/card-actionables/card-actionables.component";
|
||||||
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
import {DefaultValuePipe} from "../../_pipes/default-value.pipe";
|
||||||
|
|
@ -41,6 +41,7 @@ import {ScrollService} from "../../_services/scroll.service";
|
||||||
import {ReaderService} from "../../_services/reader.service";
|
import {ReaderService} from "../../_services/reader.service";
|
||||||
import {SeriesFormatComponent} from "../../shared/series-format/series-format.component";
|
import {SeriesFormatComponent} from "../../shared/series-format/series-format.component";
|
||||||
import {DefaultModalOptions} from "../../_models/default-modal-options";
|
import {DefaultModalOptions} from "../../_models/default-modal-options";
|
||||||
|
import {ReadingProfileService} from "../../_services/reading-profile.service";
|
||||||
|
|
||||||
function deepClone(obj: any): any {
|
function deepClone(obj: any): any {
|
||||||
if (obj === null || typeof obj !== 'object') {
|
if (obj === null || typeof obj !== 'object') {
|
||||||
|
|
@ -92,6 +93,8 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
||||||
private readonly downloadService = inject(DownloadService);
|
private readonly downloadService = inject(DownloadService);
|
||||||
private readonly scrollService = inject(ScrollService);
|
private readonly scrollService = inject(ScrollService);
|
||||||
private readonly readerService = inject(ReaderService);
|
private readonly readerService = inject(ReaderService);
|
||||||
|
private readonly readingProfilesService = inject(ReadingProfileService);
|
||||||
|
private readonly translocoService = inject(TranslocoService);
|
||||||
|
|
||||||
@Input({required: true}) series!: Series;
|
@Input({required: true}) series!: Series;
|
||||||
@Input() libraryId = 0;
|
@Input() libraryId = 0;
|
||||||
|
|
@ -277,7 +280,12 @@ export class SeriesCardComponent implements OnInit, OnChanges {
|
||||||
this.downloadService.download('series', this.series);
|
this.downloadService.download('series', this.series);
|
||||||
break;
|
break;
|
||||||
case Action.SetReadingProfile:
|
case Action.SetReadingProfile:
|
||||||
this.actionService.SetReadingProfileForMultiple([this.series]);
|
this.actionService.setReadingProfileForMultiple([series]);
|
||||||
|
break;
|
||||||
|
case Action.ClearReadingProfile:
|
||||||
|
this.readingProfilesService.clearSeriesProfiles(series.id).subscribe(() => {
|
||||||
|
this.toastr.success(this.translocoService.translate('actionable.cleared-profile'));
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ export class LibraryDetailComponent implements OnInit {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case Action.SetReadingProfile:
|
case Action.SetReadingProfile:
|
||||||
this.actionService.SetReadingProfileForMultiple(selectedSeries, (success) => {
|
this.actionService.setReadingProfileForMultiple(selectedSeries, (success) => {
|
||||||
this.bulkLoader = false;
|
this.bulkLoader = false;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
if (!success) return;
|
if (!success) return;
|
||||||
|
|
|
||||||
|
|
@ -533,21 +533,6 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
swipeToPaginate: new FormControl(this.readingProfile.swipeToPaginate)
|
swipeToPaginate: new FormControl(this.readingProfile.swipeToPaginate)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update implicit reading profile while changing settings
|
|
||||||
this.generalSettingsForm.valueChanges.pipe(
|
|
||||||
debounceTime(300),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
takeUntilDestroyed(this.destroyRef),
|
|
||||||
tap(_ => {
|
|
||||||
this.readingProfileService.updateImplicit(this.packReadingProfile(), this.seriesId).subscribe({
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
).subscribe();
|
|
||||||
|
|
||||||
|
|
||||||
this.readerModeSubject.next(this.readerMode);
|
this.readerModeSubject.next(this.readerMode);
|
||||||
this.pagingDirectionSubject.next(this.pagingDirection);
|
this.pagingDirectionSubject.next(this.pagingDirection);
|
||||||
|
|
||||||
|
|
@ -630,6 +615,26 @@ export class MangaReaderComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
|
// TODO: Fix this, it's going off way too often
|
||||||
|
// Update implicit reading profile while changing settings
|
||||||
|
this.generalSettingsForm.valueChanges.pipe(
|
||||||
|
debounceTime(300),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
map(_ => this.packReadingProfile()),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
tap(newProfile => {
|
||||||
|
this.readingProfileService.updateImplicit(newProfile, this.seriesId).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.readingProfile = newProfile;
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ import {LicenseService} from "../../../_services/license.service";
|
||||||
import {PageBookmark} from "../../../_models/readers/page-bookmark";
|
import {PageBookmark} from "../../../_models/readers/page-bookmark";
|
||||||
import {VolumeRemovedEvent} from "../../../_models/events/volume-removed-event";
|
import {VolumeRemovedEvent} from "../../../_models/events/volume-removed-event";
|
||||||
import {ReviewsComponent} from "../../../_single-module/reviews/reviews.component";
|
import {ReviewsComponent} from "../../../_single-module/reviews/reviews.component";
|
||||||
|
import {ReadingProfileService} from "../../../_services/reading-profile.service";
|
||||||
|
|
||||||
|
|
||||||
enum TabID {
|
enum TabID {
|
||||||
|
|
@ -175,6 +176,7 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
private readonly scrollService = inject(ScrollService);
|
private readonly scrollService = inject(ScrollService);
|
||||||
private readonly translocoService = inject(TranslocoService);
|
private readonly translocoService = inject(TranslocoService);
|
||||||
|
private readonly readingProfileService = inject(ReadingProfileService);
|
||||||
protected readonly bulkSelectionService = inject(BulkSelectionService);
|
protected readonly bulkSelectionService = inject(BulkSelectionService);
|
||||||
protected readonly utilityService = inject(UtilityService);
|
protected readonly utilityService = inject(UtilityService);
|
||||||
protected readonly imageService = inject(ImageService);
|
protected readonly imageService = inject(ImageService);
|
||||||
|
|
@ -610,7 +612,12 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Action.SetReadingProfile:
|
case Action.SetReadingProfile:
|
||||||
this.actionService.SetReadingProfileForMultiple([this.series]);
|
this.actionService.setReadingProfileForMultiple([this.series]);
|
||||||
|
break;
|
||||||
|
case Action.ClearReadingProfile:
|
||||||
|
this.readingProfileService.clearSeriesProfiles(this.seriesId).subscribe(() => {
|
||||||
|
this.toastr.success(this.translocoService.translate('actionable.cleared-profile'));
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {AsyncPipe, NgClass} from "@angular/common";
|
||||||
import {SideNavItemComponent} from "../side-nav-item/side-nav-item.component";
|
import {SideNavItemComponent} from "../side-nav-item/side-nav-item.component";
|
||||||
import {FilterPipe} from "../../../_pipes/filter.pipe";
|
import {FilterPipe} from "../../../_pipes/filter.pipe";
|
||||||
import {FormsModule} from "@angular/forms";
|
import {FormsModule} from "@angular/forms";
|
||||||
import {translate, TranslocoDirective} from "@jsverse/transloco";
|
import {translate, TranslocoDirective, TranslocoService} from "@jsverse/transloco";
|
||||||
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
|
import {CardActionablesComponent} from "../../../_single-module/card-actionables/card-actionables.component";
|
||||||
import {SideNavStream} from "../../../_models/sidenav/sidenav-stream";
|
import {SideNavStream} from "../../../_models/sidenav/sidenav-stream";
|
||||||
import {SideNavStreamType} from "../../../_models/sidenav/sidenav-stream-type.enum";
|
import {SideNavStreamType} from "../../../_models/sidenav/sidenav-stream-type.enum";
|
||||||
|
|
@ -25,6 +25,7 @@ import {SettingsTabId} from "../../preference-nav/preference-nav.component";
|
||||||
import {LicenseService} from "../../../_services/license.service";
|
import {LicenseService} from "../../../_services/license.service";
|
||||||
import {CdkDrag, CdkDragDrop, CdkDropList} from "@angular/cdk/drag-drop";
|
import {CdkDrag, CdkDragDrop, CdkDropList} from "@angular/cdk/drag-drop";
|
||||||
import {ToastrService} from "ngx-toastr";
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {ReadingProfileService} from "../../../_services/reading-profile.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-side-nav',
|
selector: 'app-side-nav',
|
||||||
|
|
@ -53,7 +54,9 @@ export class SideNavComponent implements OnInit {
|
||||||
protected readonly licenseService = inject(LicenseService);
|
protected readonly licenseService = inject(LicenseService);
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
private readonly actionFactoryService = inject(ActionFactoryService);
|
private readonly actionFactoryService = inject(ActionFactoryService);
|
||||||
private readonly toastr = inject(ToastrService)
|
private readonly toastr = inject(ToastrService);
|
||||||
|
private readonly readingProfilesService = inject(ReadingProfileService);
|
||||||
|
private readonly translocoService = inject(TranslocoService);
|
||||||
|
|
||||||
|
|
||||||
cachedData: SideNavStream[] | null = null;
|
cachedData: SideNavStream[] | null = null;
|
||||||
|
|
@ -175,6 +178,14 @@ export class SideNavComponent implements OnInit {
|
||||||
case (Action.Edit):
|
case (Action.Edit):
|
||||||
this.actionService.editLibrary(lib, () => window.scrollTo(0, 0));
|
this.actionService.editLibrary(lib, () => window.scrollTo(0, 0));
|
||||||
break;
|
break;
|
||||||
|
case (Action.SetReadingProfile):
|
||||||
|
this.actionService.setReadingProfileForLibrary(lib);
|
||||||
|
break;
|
||||||
|
case (Action.ClearReadingProfile):
|
||||||
|
this.readingProfilesService.clearLibraryProfiles(lib.id).subscribe(() => {
|
||||||
|
this.toastr.success(this.translocoService.translate('actionable.cleared-profile'));
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2714,7 +2714,11 @@
|
||||||
"remove-from-want-to-read-tooltip": "Remove series from Want to Read",
|
"remove-from-want-to-read-tooltip": "Remove series from Want to Read",
|
||||||
"remove-from-on-deck": "Remove From On Deck",
|
"remove-from-on-deck": "Remove From On Deck",
|
||||||
"remove-from-on-deck-tooltip": "Remove series from showing from On Deck",
|
"remove-from-on-deck-tooltip": "Remove series from showing from On Deck",
|
||||||
|
|
||||||
|
"reading-profiles": "Reading Profiles",
|
||||||
"set-reading-profile": "Set Reading Profile",
|
"set-reading-profile": "Set Reading Profile",
|
||||||
|
"clear-reading-profile": "Clear Reading Profile",
|
||||||
|
"cleared-profile": "Cleared Reading Profile",
|
||||||
|
|
||||||
"others": "Others",
|
"others": "Others",
|
||||||
"add-to-reading-list": "Add to Reading List",
|
"add-to-reading-list": "Add to Reading List",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue