mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Lots of Filtering Fixes & New Fields (#2244)
* Added an id for komf userscript to help it inject into Kavita's UI without relying on strings, given localization. * Still working the filter fields, there is a bug with selecting an input and it setting undefined like crazy. Path is coded but not tested or validated. * Stashing changed. Really not sure what's happening. I'm seeing 2 constructor calls for one row. I'm seeing a field change trigger 400 events. Values aren't getting set correctly on default string. I've made a ton of changes, when resuming this work, look at the diff. All of this can be reset excluding the Path work. * Lots of comments but the double instantiation is due to the mobile drawer. Added an ngIf which seems to work. * Fixed dropdown options triggering a ton of looped calls. Default limitTo to 0 when user empties blank or negative. * Removed a ton of UserId db calls from a ton of apis. Added a new API to allow UI to query a specific role to lessen load on UI. * Optimized the code on new filtering to only load people by a given role. This should speed up heavily tagged libraries. Commented out a bunch of code that's no longer used. Will be cleaned up later. * Fixed support so that library filter can handle multiple selections. * Fixed a bug when hitting enter in an input, the statement would be removed. * Fixed multi-select not resuming from url correctly. * Restored the series/all api for Tachiyomi to continue using until I'm motivated enough to update the extension. * Fixed some resuming of state with dropdowns, not always setting values in correct order. * Added FilePath Filter which lets a user search on individual files (slow, may need index) * Added a full filepath for new filtering.
This commit is contained in:
parent
69b5530a93
commit
cd84913fb9
@ -87,8 +87,7 @@ public class DeviceController : BaseApiController
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<DeviceDto>>> GetDevices()
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.DeviceRepository.GetDevicesForUserAsync(userId));
|
||||
return Ok(await _unitOfWork.DeviceRepository.GetDevicesForUserAsync(User.GetUserId()));
|
||||
}
|
||||
|
||||
[HttpPost("send-to")]
|
||||
@ -100,7 +99,7 @@ public class DeviceController : BaseApiController
|
||||
if (await _emailService.IsDefaultEmailService())
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "send-to-kavita-email"));
|
||||
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
await _eventHub.SendMessageToAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"),
|
||||
"started"), userId);
|
||||
@ -134,7 +133,7 @@ public class DeviceController : BaseApiController
|
||||
if (await _emailService.IsDefaultEmailService())
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "send-to-kavita-email"));
|
||||
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
await _eventHub.SendMessageToAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"),
|
||||
"started"), userId);
|
||||
|
@ -161,8 +161,7 @@ public class LibraryController : BaseApiController
|
||||
[HttpGet("jump-bar")]
|
||||
public async Task<ActionResult<IEnumerable<JumpKeyDto>>> GetJumpBar(int libraryId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
if (!await _unitOfWork.UserRepository.HasAccessToLibrary(libraryId, userId))
|
||||
if (!await _unitOfWork.UserRepository.HasAccessToLibrary(libraryId, User.GetUserId()))
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-library-access"));
|
||||
|
||||
return Ok(_unitOfWork.LibraryRepository.GetJumpBarAsync(libraryId));
|
||||
|
@ -37,17 +37,28 @@ public class MetadataController : BaseApiController
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})]
|
||||
public async Task<ActionResult<IList<GenreTagDto>>> GetAllGenres(string? libraryIds)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
|
||||
if (ids != null && ids.Count > 0)
|
||||
{
|
||||
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(ids, userId));
|
||||
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(ids, User.GetUserId()));
|
||||
}
|
||||
|
||||
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosAsync(userId));
|
||||
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosAsync(User.GetUserId()));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fetches people from the instance by role
|
||||
/// </summary>
|
||||
/// <param name="role">role</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("people-by-role")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"role"})]
|
||||
public async Task<ActionResult<IList<PersonDto>>> GetAllPeople(PersonRole? role)
|
||||
{
|
||||
return role.HasValue ?
|
||||
Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosByRoleAsync(User.GetUserId(), role!.Value)) :
|
||||
Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosAsync(User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches people from the instance
|
||||
@ -58,13 +69,12 @@ public class MetadataController : BaseApiController
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})]
|
||||
public async Task<ActionResult<IList<PersonDto>>> GetAllPeople(string? libraryIds)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
|
||||
if (ids != null && ids.Count > 0)
|
||||
{
|
||||
return Ok(await _unitOfWork.PersonRepository.GetAllPeopleDtosForLibrariesAsync(ids, userId));
|
||||
return Ok(await _unitOfWork.PersonRepository.GetAllPeopleDtosForLibrariesAsync(ids, User.GetUserId()));
|
||||
}
|
||||
return Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosAsync(userId));
|
||||
return Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosAsync(User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -76,13 +86,12 @@ public class MetadataController : BaseApiController
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})]
|
||||
public async Task<ActionResult<IList<TagDto>>> GetAllTags(string? libraryIds)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
|
||||
if (ids != null && ids.Count > 0)
|
||||
{
|
||||
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(ids, userId));
|
||||
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(ids, User.GetUserId()));
|
||||
}
|
||||
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosAsync(userId));
|
||||
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosAsync(User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -39,8 +39,7 @@ public class ReadingListController : BaseApiController
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetList(int readingListId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, userId));
|
||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -54,8 +53,7 @@ public class ReadingListController : BaseApiController
|
||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetListsForUser([FromQuery] UserParams userParams,
|
||||
bool includePromoted = true, bool sortByLastModified = false)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, includePromoted,
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(User.GetUserId(), includePromoted,
|
||||
userParams, sortByLastModified);
|
||||
Response.AddPaginationHeader(items.CurrentPage, items.PageSize, items.TotalCount, items.TotalPages);
|
||||
|
||||
@ -70,10 +68,8 @@ public class ReadingListController : BaseApiController
|
||||
[HttpGet("lists-for-series")]
|
||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetListsForSeries(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForSeriesAndUserAsync(userId, seriesId, true);
|
||||
|
||||
return Ok(items);
|
||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtosForSeriesAndUserAsync(User.GetUserId(),
|
||||
seriesId, true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -62,7 +62,7 @@ public class ReviewController : BaseApiController
|
||||
}
|
||||
|
||||
var cacheKey = CacheKey + seriesId;
|
||||
IEnumerable<UserReviewDto> externalReviews;
|
||||
IList<UserReviewDto> externalReviews;
|
||||
|
||||
var result = await _cacheProvider.GetAsync<IEnumerable<UserReviewDto>>(cacheKey);
|
||||
if (result.HasValue)
|
||||
@ -74,7 +74,6 @@ public class ReviewController : BaseApiController
|
||||
var reviews = (await _reviewService.GetReviewsForSeries(userId, seriesId)).ToList();
|
||||
externalReviews = SelectSpectrumOfReviews(reviews);
|
||||
|
||||
|
||||
await _cacheProvider.SetAsync(cacheKey, externalReviews, TimeSpan.FromHours(10));
|
||||
_logger.LogDebug("Caching external reviews for {Key}", cacheKey);
|
||||
}
|
||||
@ -87,7 +86,7 @@ public class ReviewController : BaseApiController
|
||||
return Ok(userRatings);
|
||||
}
|
||||
|
||||
private static IList<UserReviewDto> SelectSpectrumOfReviews(List<UserReviewDto> reviews)
|
||||
private static IList<UserReviewDto> SelectSpectrumOfReviews(IList<UserReviewDto> reviews)
|
||||
{
|
||||
IList<UserReviewDto> externalReviews;
|
||||
var totalReviews = reviews.Count;
|
||||
|
@ -33,8 +33,7 @@ public class SearchController : BaseApiController
|
||||
[HttpGet("series-for-mangafile")]
|
||||
public async Task<ActionResult<SeriesDto>> GetSeriesForMangaFile(int mangaFileId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesForMangaFile(mangaFileId, userId));
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesForMangaFile(mangaFileId, User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -46,8 +45,7 @@ public class SearchController : BaseApiController
|
||||
[HttpGet("series-for-chapter")]
|
||||
public async Task<ActionResult<SeriesDto>> GetSeriesForChapter(int chapterId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesForChapter(chapterId, userId));
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesForChapter(chapterId, User.GetUserId()));
|
||||
}
|
||||
|
||||
[HttpGet("search")]
|
||||
|
@ -67,7 +67,7 @@ public class SeriesController : BaseApiController
|
||||
[Obsolete("use v2")]
|
||||
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId, [FromQuery] UserParams userParams, [FromBody] FilterDto filterDto)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto);
|
||||
|
||||
@ -90,7 +90,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpPost("v2")]
|
||||
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibraryV2([FromQuery] UserParams userParams, [FromBody] FilterV2Dto filterDto)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdV2Async(userId, userParams, filterDto);
|
||||
|
||||
@ -114,8 +114,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpGet("{seriesId:int}")]
|
||||
public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, User.GetUserId());
|
||||
if (series == null) return NoContent();
|
||||
return Ok(series);
|
||||
}
|
||||
@ -150,15 +149,13 @@ public class SeriesController : BaseApiController
|
||||
[HttpGet("volumes")]
|
||||
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.VolumeRepository.GetVolumesDtoAsync(seriesId, userId));
|
||||
return Ok(await _unitOfWork.VolumeRepository.GetVolumesDtoAsync(seriesId, User.GetUserId()));
|
||||
}
|
||||
|
||||
[HttpGet("volume")]
|
||||
public async Task<ActionResult<VolumeDto?>> GetVolume(int volumeId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var vol = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, userId);
|
||||
var vol = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, User.GetUserId());
|
||||
if (vol == null) return NoContent();
|
||||
return Ok(vol);
|
||||
}
|
||||
@ -253,7 +250,7 @@ public class SeriesController : BaseApiController
|
||||
[Obsolete("use recently-added-v2")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAdded(FilterDto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId, userId, userParams, filterDto);
|
||||
|
||||
@ -277,7 +274,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpPost("recently-added-v2")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAddedV2(FilterV2Dto filterDto, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetRecentlyAddedV2(userId, userParams, filterDto);
|
||||
|
||||
@ -299,8 +296,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpPost("recently-updated-series")]
|
||||
public async Task<ActionResult<IEnumerable<RecentlyAddedItemDto>>> GetRecentlyAddedChapters()
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyUpdatedSeries(userId, 20));
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyUpdatedSeries(User.GetUserId(), 20));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -310,10 +306,10 @@ public class SeriesController : BaseApiController
|
||||
/// <param name="userParams"></param>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("all")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetAllSeries(FilterV2Dto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0)
|
||||
[HttpPost("all-v2")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetAllSeriesV2(FilterV2Dto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdV2Async(userId, userParams, filterDto);
|
||||
|
||||
@ -327,6 +323,31 @@ public class SeriesController : BaseApiController
|
||||
return Ok(series);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all series for the library. Obsolete, use all-v2
|
||||
/// </summary>
|
||||
/// <param name="filterDto"></param>
|
||||
/// <param name="userParams"></param>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("all")]
|
||||
[Obsolete("User all-v2")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetAllSeries(FilterDto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto);
|
||||
|
||||
// Apply progress/rating information (I can't work out how to do this in initial query)
|
||||
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series"));
|
||||
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
|
||||
return Ok(series);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches series that are on deck aka have progress on them.
|
||||
/// </summary>
|
||||
@ -337,7 +358,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpPost("on-deck")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetOnDeck([FromQuery] UserParams userParams, [FromQuery] int libraryId = 0)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
var pagedList = await _unitOfWork.SeriesRepository.GetOnDeck(userId, libraryId, userParams, null);
|
||||
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, pagedList);
|
||||
@ -419,25 +440,24 @@ public class SeriesController : BaseApiController
|
||||
[HttpPost("metadata")]
|
||||
public async Task<ActionResult> UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto)
|
||||
{
|
||||
if (await _seriesService.UpdateSeriesMetadata(updateSeriesMetadataDto))
|
||||
if (!await _seriesService.UpdateSeriesMetadata(updateSeriesMetadataDto))
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "update-metadata-fail"));
|
||||
|
||||
if (await _licenseService.HasActiveLicense())
|
||||
{
|
||||
if (await _licenseService.HasActiveLicense())
|
||||
_logger.LogDebug("Clearing cache as series weblinks may have changed");
|
||||
await _reviewCacheProvider.RemoveAsync(ReviewController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId);
|
||||
await _ratingCacheProvider.RemoveAsync(RatingController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId);
|
||||
|
||||
var allUsers = (await _unitOfWork.UserRepository.GetAllUsersAsync()).Select(s => s.Id);
|
||||
foreach (var userId in allUsers)
|
||||
{
|
||||
_logger.LogDebug("Clearing cache as series weblinks may have changed");
|
||||
await _reviewCacheProvider.RemoveAsync(ReviewController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId);
|
||||
await _ratingCacheProvider.RemoveAsync(RatingController.CacheKey + updateSeriesMetadataDto.SeriesMetadata.SeriesId);
|
||||
|
||||
var allUsers = (await _unitOfWork.UserRepository.GetAllUsersAsync()).Select(s => s.Id);
|
||||
foreach (var userId in allUsers)
|
||||
{
|
||||
await _recommendationCacheProvider.RemoveAsync(RecommendedController.CacheKey + $"{updateSeriesMetadataDto.SeriesMetadata.SeriesId}-{userId}");
|
||||
}
|
||||
await _recommendationCacheProvider.RemoveAsync(RecommendedController.CacheKey + $"{updateSeriesMetadataDto.SeriesMetadata.SeriesId}-{userId}");
|
||||
}
|
||||
|
||||
return Ok(await _localizationService.Translate(User.GetUserId(), "series-updated"));
|
||||
}
|
||||
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "update-metadata-fail"));
|
||||
return Ok(await _localizationService.Translate(User.GetUserId(), "series-updated"));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -449,7 +469,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpGet("series-by-collection")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetSeriesByCollectionTag(int collectionId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForCollectionAsync(collectionId, userId, userParams);
|
||||
|
||||
@ -472,8 +492,7 @@ public class SeriesController : BaseApiController
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetAllSeriesById(SeriesByIdsDto dto)
|
||||
{
|
||||
if (dto.SeriesIds == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-payload"));
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForIdsAsync(dto.SeriesIds, userId));
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForIdsAsync(dto.SeriesIds, User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -503,10 +522,9 @@ public class SeriesController : BaseApiController
|
||||
[HttpGet("series-detail")]
|
||||
public async Task<ActionResult<SeriesDetailDto>> GetSeriesDetailBreakdown(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
try
|
||||
{
|
||||
return await _seriesService.GetSeriesDetail(seriesId, userId);
|
||||
return await _seriesService.GetSeriesDetail(seriesId, User.GetUserId());
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
@ -525,9 +543,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpGet("related")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRelatedSeries(int seriesId, RelationKind relation)
|
||||
{
|
||||
// Send back a custom DTO with each type or maybe sorted in some way
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesForRelationKind(userId, seriesId, relation));
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetSeriesForRelationKind(User.GetUserId(), seriesId, relation));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -538,8 +554,7 @@ public class SeriesController : BaseApiController
|
||||
[HttpGet("all-related")]
|
||||
public async Task<ActionResult<RelatedSeriesDto>> GetAllRelatedSeries(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _seriesService.GetRelatedSeries(userId, seriesId));
|
||||
return Ok(await _seriesService.GetRelatedSeries(User.GetUserId(), seriesId));
|
||||
}
|
||||
|
||||
|
||||
|
@ -68,10 +68,9 @@ public class UsersController : BaseApiController
|
||||
[HttpGet("has-reading-progress")]
|
||||
public async Task<ActionResult<bool>> HasReadingProgress(int libraryId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId);
|
||||
if (library == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "library-doesnt-exist"));
|
||||
return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, userId));
|
||||
return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, User.GetUserId()));
|
||||
}
|
||||
|
||||
[HttpGet("has-library-access")]
|
||||
|
@ -28,5 +28,13 @@ public enum FilterField
|
||||
ReadProgress = 20,
|
||||
Formats = 21,
|
||||
ReleaseYear = 22,
|
||||
ReadTime = 23
|
||||
ReadTime = 23,
|
||||
/// <summary>
|
||||
/// Series Folder
|
||||
/// </summary>
|
||||
Path = 24,
|
||||
/// <summary>
|
||||
/// File path
|
||||
/// </summary>
|
||||
FilePath = 25
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Extensions.QueryExtensions;
|
||||
using AutoMapper;
|
||||
@ -17,9 +18,11 @@ public interface IPersonRepository
|
||||
void Remove(Person person);
|
||||
Task<IList<Person>> GetAllPeople();
|
||||
Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId);
|
||||
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role);
|
||||
Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false);
|
||||
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(List<int> libraryIds, int userId);
|
||||
Task<int> GetCountAsync();
|
||||
|
||||
}
|
||||
|
||||
public class PersonRepository : IPersonRepository
|
||||
@ -94,4 +97,15 @@ public class PersonRepository : IPersonRepository
|
||||
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role)
|
||||
{
|
||||
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
return await _context.Person
|
||||
.Where(p => p.Role == role)
|
||||
.OrderBy(p => p.Name)
|
||||
.RestrictAgainstAgeRestriction(ageRating)
|
||||
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
@ -840,7 +840,6 @@ public class SeriesRepository : ISeriesRepository
|
||||
private async Task<IQueryable<Series>> CreateFilteredSearchQueryable(int userId, int libraryId, FilterDto filter, QueryContext queryContext)
|
||||
{
|
||||
// NOTE: Why do we even have libraryId when the filter has the actual libraryIds?
|
||||
// TODO: Remove this method
|
||||
var userLibraries = await GetUserLibrariesForFilteredQuery(libraryId, userId, queryContext);
|
||||
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||
var onlyParentSeries = await _context.AppUserPreferences.Where(u => u.AppUserId == userId)
|
||||
@ -869,7 +868,7 @@ public class SeriesRepository : ISeriesRepository
|
||||
.HasFormat(filter.Formats != null && filter.Formats.Count > 0, FilterComparison.Contains, filter.Formats!)
|
||||
.HasAverageReadTime(true, FilterComparison.GreaterThanEqual, 0)
|
||||
|
||||
// This needs different treatment
|
||||
// TODO: This needs different treatment
|
||||
.HasPeople(hasPeopleFilter, FilterComparison.Contains, allPeopleIds)
|
||||
|
||||
.WhereIf(onlyParentSeries,
|
||||
@ -979,11 +978,12 @@ public class SeriesRepository : ISeriesRepository
|
||||
{
|
||||
if (stmt.Comparison is FilterComparison.Equal or FilterComparison.Contains)
|
||||
{
|
||||
filterIncludeLibs.Add(int.Parse(stmt.Value));
|
||||
|
||||
filterIncludeLibs.AddRange(stmt.Value.Split(',').Select(int.Parse));
|
||||
}
|
||||
else
|
||||
{
|
||||
filterExcludeLibs.Add(int.Parse(stmt.Value));
|
||||
filterExcludeLibs.AddRange(stmt.Value.Split(',').Select(int.Parse));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1036,6 +1036,8 @@ public class SeriesRepository : ISeriesRepository
|
||||
{
|
||||
FilterField.Summary => query.HasSummary(true, statement.Comparison, (string) value),
|
||||
FilterField.SeriesName => query.HasName(true, statement.Comparison, (string) value),
|
||||
FilterField.Path => query.HasPath(true, statement.Comparison, (string) value),
|
||||
FilterField.FilePath => query.HasFilePath(true, statement.Comparison, (string) value),
|
||||
FilterField.PublicationStatus => query.HasPublicationStatus(true, statement.Comparison,
|
||||
(IList<PublicationStatus>) value),
|
||||
FilterField.Languages => query.HasLanguage(true, statement.Comparison, (IList<string>) value),
|
||||
|
@ -6,6 +6,7 @@ using System.Linq.Expressions;
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
using Kavita.Common;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@ -512,4 +513,116 @@ public static class SeriesFilter
|
||||
throw new ArgumentOutOfRangeException(nameof(comparison), comparison, "Filter Comparison is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public static IQueryable<Series> HasPath(this IQueryable<Series> queryable, bool condition,
|
||||
FilterComparison comparison, string queryString)
|
||||
{
|
||||
if (!condition) return queryable;
|
||||
|
||||
var normalizedPath = Parser.NormalizePath(queryString);
|
||||
|
||||
switch (comparison)
|
||||
{
|
||||
case FilterComparison.Equal:
|
||||
return queryable.Where(s => s.FolderPath != null && s.FolderPath.Equals(normalizedPath));
|
||||
case FilterComparison.BeginsWith:
|
||||
return queryable.Where(s => s.FolderPath != null && EF.Functions.Like(s.FolderPath, $"{normalizedPath}%"));
|
||||
case FilterComparison.EndsWith:
|
||||
return queryable.Where(s => s.FolderPath != null && EF.Functions.Like(s.FolderPath, $"%{normalizedPath}"));
|
||||
case FilterComparison.Matches:
|
||||
return queryable.Where(s => s.FolderPath != null && EF.Functions.Like(s.FolderPath, $"%{normalizedPath}%"));
|
||||
case FilterComparison.NotEqual:
|
||||
return queryable.Where(s => s.FolderPath != null && s.FolderPath != normalizedPath);
|
||||
case FilterComparison.NotContains:
|
||||
case FilterComparison.GreaterThan:
|
||||
case FilterComparison.GreaterThanEqual:
|
||||
case FilterComparison.LessThan:
|
||||
case FilterComparison.LessThanEqual:
|
||||
case FilterComparison.Contains:
|
||||
case FilterComparison.IsBefore:
|
||||
case FilterComparison.IsAfter:
|
||||
case FilterComparison.IsInLast:
|
||||
case FilterComparison.IsNotInLast:
|
||||
throw new KavitaException($"{comparison} not applicable for Series.FolderPath");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(comparison), comparison, "Filter Comparison is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public static IQueryable<Series> HasFilePath(this IQueryable<Series> queryable, bool condition,
|
||||
FilterComparison comparison, string queryString)
|
||||
{
|
||||
if (!condition) return queryable;
|
||||
|
||||
var normalizedPath = Parser.NormalizePath(queryString);
|
||||
|
||||
switch (comparison)
|
||||
{
|
||||
case FilterComparison.Equal:
|
||||
return queryable.Where(s =>
|
||||
s.Volumes.Any(v =>
|
||||
v.Chapters.Any(c =>
|
||||
c.Files.Any(f =>
|
||||
f.FilePath != null && f.FilePath.Equals(normalizedPath)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
case FilterComparison.BeginsWith:
|
||||
return queryable.Where(s =>
|
||||
s.Volumes.Any(v =>
|
||||
v.Chapters.Any(c =>
|
||||
c.Files.Any(f =>
|
||||
f.FilePath != null && EF.Functions.Like(f.FilePath, $"{normalizedPath}%")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
case FilterComparison.EndsWith:
|
||||
return queryable.Where(s =>
|
||||
s.Volumes.Any(v =>
|
||||
v.Chapters.Any(c =>
|
||||
c.Files.Any(f =>
|
||||
f.FilePath != null && EF.Functions.Like(f.FilePath, $"%{normalizedPath}")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
case FilterComparison.Matches:
|
||||
return queryable.Where(s =>
|
||||
s.Volumes.Any(v =>
|
||||
v.Chapters.Any(c =>
|
||||
c.Files.Any(f =>
|
||||
f.FilePath != null && EF.Functions.Like(f.FilePath, $"%{normalizedPath}%")
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
case FilterComparison.NotEqual:
|
||||
return queryable.Where(s =>
|
||||
s.Volumes.Any(v =>
|
||||
v.Chapters.Any(c =>
|
||||
c.Files.Any(f =>
|
||||
f.FilePath == null || !f.FilePath.Equals(normalizedPath)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
case FilterComparison.NotContains:
|
||||
case FilterComparison.GreaterThan:
|
||||
case FilterComparison.GreaterThanEqual:
|
||||
case FilterComparison.LessThan:
|
||||
case FilterComparison.LessThanEqual:
|
||||
case FilterComparison.Contains:
|
||||
case FilterComparison.IsBefore:
|
||||
case FilterComparison.IsAfter:
|
||||
case FilterComparison.IsInLast:
|
||||
case FilterComparison.IsNotInLast:
|
||||
throw new KavitaException($"{comparison} not applicable for Series.FolderPath");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(comparison), comparison, "Filter Comparison is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ public static class FilterFieldValueConverter
|
||||
return field switch
|
||||
{
|
||||
FilterField.SeriesName => (value, typeof(string)),
|
||||
FilterField.Path => (value, typeof(string)),
|
||||
FilterField.FilePath => (value, typeof(string)),
|
||||
FilterField.ReleaseYear => (int.Parse(value), typeof(int)),
|
||||
FilterField.Languages => (value.Split(',').ToList(), typeof(IList<string>)),
|
||||
FilterField.PublicationStatus => (value.Split(',')
|
||||
|
8
UI/Web/package-lock.json
generated
8
UI/Web/package-lock.json
generated
@ -37,7 +37,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"lazysizes": "^5.3.2",
|
||||
"ng-circle-progress": "^1.7.1",
|
||||
"ng-select2-component": "^13.0.2",
|
||||
"ng-select2-component": "^13.0.6",
|
||||
"ngx-color-picker": "^14.0.0",
|
||||
"ngx-extended-pdf-viewer": "^16.2.16",
|
||||
"ngx-file-drop": "^16.0.0",
|
||||
@ -10558,9 +10558,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ng-select2-component": {
|
||||
"version": "13.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-13.0.2.tgz",
|
||||
"integrity": "sha512-8Tms5p0V/0J0vCWOf2Vrk6tJlwbaf3D3As3iigcjRncYlfXN130agniBcZ007C3zK2KyLXJJRkEWzlCls8/TVQ==",
|
||||
"version": "13.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ng-select2-component/-/ng-select2-component-13.0.6.tgz",
|
||||
"integrity": "sha512-CiAelglSz2aeYy0BiXRi32zc49Mq27+J1eDzTrXmf2o50MvNo3asS3NRVQcnSldo/zLcJafWCMueVfjVaV1etw==",
|
||||
"dependencies": {
|
||||
"ngx-infinite-scroll": ">=16.0.0",
|
||||
"tslib": "^2.3.0"
|
||||
|
@ -42,7 +42,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"lazysizes": "^5.3.2",
|
||||
"ng-circle-progress": "^1.7.1",
|
||||
"ng-select2-component": "^13.0.2",
|
||||
"ng-select2-component": "^13.0.6",
|
||||
"ngx-color-picker": "^14.0.0",
|
||||
"ngx-extended-pdf-viewer": "^16.2.16",
|
||||
"ngx-file-drop": "^16.0.0",
|
||||
|
@ -24,7 +24,9 @@ export enum FilterField
|
||||
ReadProgress = 20,
|
||||
Formats = 21,
|
||||
ReleaseYear = 22,
|
||||
ReadTime = 23
|
||||
ReadTime = 23,
|
||||
Path = 24,
|
||||
FilePath = 25
|
||||
}
|
||||
|
||||
export const allFields = Object.keys(FilterField)
|
||||
|
@ -8,7 +8,7 @@ import {AgeRating} from '../_models/metadata/age-rating';
|
||||
import {AgeRatingDto} from '../_models/metadata/age-rating-dto';
|
||||
import {Language} from '../_models/metadata/language';
|
||||
import {PublicationStatusDto} from '../_models/metadata/publication-status-dto';
|
||||
import {Person} from '../_models/metadata/person';
|
||||
import {Person, PersonRole} from '../_models/metadata/person';
|
||||
import {Tag} from '../_models/tag';
|
||||
import {TextResonse} from '../_types/text-response';
|
||||
import {FilterComparison} from '../_models/metadata/v2/filter-comparison';
|
||||
@ -33,44 +33,44 @@ export class MetadataService {
|
||||
|
||||
constructor(private httpClient: HttpClient, private router: Router) { }
|
||||
|
||||
applyFilter(page: Array<any>, filter: FilterField, comparison: FilterComparison, value: string) {
|
||||
const dto: SeriesFilterV2 = {
|
||||
statements: [this.createDefaultFilterStatement(filter, comparison, value + '')],
|
||||
combination: FilterCombination.Or,
|
||||
limitTo: 0
|
||||
};
|
||||
//
|
||||
// console.log('navigating to: ', this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
|
||||
// this.router.navigateByUrl(this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
|
||||
// applyFilter(page: Array<any>, filter: FilterField, comparison: FilterComparison, value: string) {
|
||||
// const dto: SeriesFilterV2 = {
|
||||
// statements: [this.createDefaultFilterStatement(filter, comparison, value + '')],
|
||||
// combination: FilterCombination.Or,
|
||||
// limitTo: 0
|
||||
// };
|
||||
// //
|
||||
// // console.log('navigating to: ', this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
|
||||
// // this.router.navigateByUrl(this.filterUtilityService.urlFromFilterV2(page.join('/'), dto));
|
||||
//
|
||||
// // Creates a temp name for the filter
|
||||
// this.httpClient.post<string>(this.baseUrl + 'filter/create-temp', dto, TextResonse).pipe(map(name => {
|
||||
// dto.name = name;
|
||||
// }), switchMap((_) => {
|
||||
// let params: any = {};
|
||||
// params['filterName'] = dto.name;
|
||||
// return this.router.navigate(page, {queryParams: params});
|
||||
// })).subscribe();
|
||||
//
|
||||
// }
|
||||
|
||||
// Creates a temp name for the filter
|
||||
this.httpClient.post<string>(this.baseUrl + 'filter/create-temp', dto, TextResonse).pipe(map(name => {
|
||||
dto.name = name;
|
||||
}), switchMap((_) => {
|
||||
let params: any = {};
|
||||
params['filterName'] = dto.name;
|
||||
return this.router.navigate(page, {queryParams: params});
|
||||
})).subscribe();
|
||||
// getFilter(filterName: string) {
|
||||
// return this.httpClient.get<SeriesFilterV2>(this.baseUrl + 'filter?name=' + filterName);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
getFilter(filterName: string) {
|
||||
return this.httpClient.get<SeriesFilterV2>(this.baseUrl + 'filter?name=' + filterName);
|
||||
}
|
||||
|
||||
getAgeRating(ageRating: AgeRating) {
|
||||
if (this.ageRatingTypes != undefined && this.ageRatingTypes.hasOwnProperty(ageRating)) {
|
||||
return of(this.ageRatingTypes[ageRating]);
|
||||
}
|
||||
return this.httpClient.get<string>(this.baseUrl + 'series/age-rating?ageRating=' + ageRating, TextResonse).pipe(map(ratingString => {
|
||||
if (this.ageRatingTypes === undefined) {
|
||||
this.ageRatingTypes = {};
|
||||
}
|
||||
|
||||
this.ageRatingTypes[ageRating] = ratingString;
|
||||
return this.ageRatingTypes[ageRating];
|
||||
}));
|
||||
}
|
||||
// getAgeRating(ageRating: AgeRating) {
|
||||
// if (this.ageRatingTypes != undefined && this.ageRatingTypes.hasOwnProperty(ageRating)) {
|
||||
// return of(this.ageRatingTypes[ageRating]);
|
||||
// }
|
||||
// return this.httpClient.get<string>(this.baseUrl + 'series/age-rating?ageRating=' + ageRating, TextResonse).pipe(map(ratingString => {
|
||||
// if (this.ageRatingTypes === undefined) {
|
||||
// this.ageRatingTypes = {};
|
||||
// }
|
||||
//
|
||||
// this.ageRatingTypes[ageRating] = ratingString;
|
||||
// return this.ageRatingTypes[ageRating];
|
||||
// }));
|
||||
// }
|
||||
|
||||
getAllAgeRatings(libraries?: Array<number>) {
|
||||
let method = 'metadata/age-ratings'
|
||||
@ -132,10 +132,14 @@ export class MetadataService {
|
||||
return this.httpClient.get<Array<Person>>(this.baseUrl + method);
|
||||
}
|
||||
|
||||
getChapterSummary(chapterId: number) {
|
||||
return this.httpClient.get<string>(this.baseUrl + 'metadata/chapter-summary?chapterId=' + chapterId, TextResonse);
|
||||
getAllPeopleByRole(role: PersonRole) {
|
||||
return this.httpClient.get<Array<Person>>(this.baseUrl + 'metadata/people-by-role?role=' + role);
|
||||
}
|
||||
|
||||
// getChapterSummary(chapterId: number) {
|
||||
// return this.httpClient.get<string>(this.baseUrl + 'metadata/chapter-summary?chapterId=' + chapterId, TextResonse);
|
||||
// }
|
||||
|
||||
createDefaultFilterDto(): SeriesFilterV2 {
|
||||
return {
|
||||
statements: [] as FilterStatement[],
|
||||
@ -159,6 +163,6 @@ export class MetadataService {
|
||||
updateFilter(arr: Array<FilterStatement>, index: number, filterStmt: FilterStatement) {
|
||||
arr[index].comparison = filterStmt.comparison;
|
||||
arr[index].field = filterStmt.field;
|
||||
arr[index].value = filterStmt.value + '';
|
||||
arr[index].value = filterStmt.value ? filterStmt.value + '' : '';
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ export class SeriesService {
|
||||
params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage);
|
||||
const data = filter || {};
|
||||
|
||||
return this.httpClient.post<PaginatedResult<Series[]>>(this.baseUrl + 'series/all', data, {observe: 'response', params}).pipe(
|
||||
return this.httpClient.post<PaginatedResult<Series[]>>(this.baseUrl + 'series/all-v2', data, {observe: 'response', params}).pipe(
|
||||
map((response: any) => {
|
||||
return this.utilityService.createPaginatedResult(response, this.paginatedResults);
|
||||
})
|
||||
|
@ -115,12 +115,14 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('[card-detail-layout] ngOnInit')
|
||||
if (this.trackByIdentity === undefined) {
|
||||
this.trackByIdentity = (_: number, item: any) => `${this.header}_${this.updateApplied}_${item?.libraryId}`;
|
||||
}
|
||||
|
||||
if (this.filterSettings === undefined) {
|
||||
this.filterSettings = new FilterSettings();
|
||||
console.log('[card-detail-layout] creating blank FilterSettings');
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
@ -178,6 +180,7 @@ export class CardDetailLayoutComponent implements OnInit, OnChanges {
|
||||
this.applyFilter.emit(event);
|
||||
this.updateApplied++;
|
||||
this.filter = event.filterV2;
|
||||
console.log('[card-detail-layout] apply filter')
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
|
@ -135,6 +135,11 @@ export class LibraryDetailComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
get Debug() {
|
||||
console.log('rendered section ');
|
||||
return 0;
|
||||
}
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router, private seriesService: SeriesService,
|
||||
private libraryService: LibraryService, private titleService: Title, private actionFactoryService: ActionFactoryService,
|
||||
private actionService: ActionService, public bulkSelectionService: BulkSelectionService, private hubService: MessageHubService,
|
||||
|
@ -1,9 +1,8 @@
|
||||
<ng-container *transloco="let t; read: 'metadata-builder'">
|
||||
<ng-container *ngIf="filter">
|
||||
|
||||
<ng-container *ngIf="utilityService.getActiveBreakpoint() === Breakpoint.Desktop; else mobileView">
|
||||
<div class="container-fluid">
|
||||
<form [formGroup]="formGroup">
|
||||
<form [formGroup]="formGroup">
|
||||
<ng-container *ngIf="utilityService.getActiveBreakpoint() === Breakpoint.Desktop; else mobileView">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-2">
|
||||
<select class="form-select" formControlName="comparison">
|
||||
@ -12,33 +11,30 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-icon" (click)="addFilter()" [ngbTooltip]="t('add-rule')" [disabled]="statementLimit === -1 || (statementLimit > 0 && filter.statements.length >= statementLimit)">
|
||||
<button type="button" class="btn btn-icon" (click)="addFilter()" [ngbTooltip]="t('add-rule')" [disabled]="statementLimit === -1 || (statementLimit > 0 && filter.statements.length >= statementLimit)">
|
||||
<i class="fa fa-solid fa-plus" aria-hidden="true"></i>
|
||||
<span class="visually-hidden" aria-hidden="true">{{t('add-rule')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row mb-2" *ngFor="let filterStmt of filter.statements; let i = index">
|
||||
<div class="col-md-10">
|
||||
<app-metadata-row-filter [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
|
||||
<div class="col-md-1 ms-2">
|
||||
<button class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule', {num: i})" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
|
||||
<i class="fa-solid fa-minus" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('remove-rule', {num: i})}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</app-metadata-row-filter>
|
||||
<div class="row mb-2" *ngFor="let filterStmt of filter.statements; let i = index">
|
||||
<div class="col-md-10">
|
||||
<app-metadata-row-filter [index]="i + 100" [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
|
||||
<div class="col-md-1 ms-2">
|
||||
<button type="button" class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule', {num: i})" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
|
||||
<i class="fa-solid fa-minus" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('remove-rule', {num: i})}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</app-metadata-row-filter>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #mobileView>
|
||||
<!-- TODO: Robbie please help me style this drawer only view -->
|
||||
<div class="container-fluid">
|
||||
<form [formGroup]="formGroup">
|
||||
<ng-template #mobileView>
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-2 col-10">
|
||||
<select class="form-select" formControlName="comparison">
|
||||
@ -53,22 +49,21 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="row mb-3" *ngFor="let filterStmt of filter.statements; let i = index">
|
||||
<div class="col-md-12">
|
||||
<app-metadata-row-filter [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
|
||||
<div class="col-md-1 ms-2 col-1">
|
||||
<button class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule')" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
|
||||
<i class="fa-solid fa-minus" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('remove-rule')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</app-metadata-row-filter>
|
||||
<div class="row mb-3" *ngFor="let filterStmt of filter.statements; let i = index">
|
||||
<div class="col-md-12">
|
||||
<app-metadata-row-filter [index]="i" [preset]="filterStmt" [availableFields]="availableFilterFields" (filterStatement)="updateFilter(i, $event)">
|
||||
<div class="col-md-1 ms-2 col-1">
|
||||
<button type="button" class="btn btn-icon" #removeBtn [ngbTooltip]="t('remove-rule')" (click)="removeFilter(i)" *ngIf="i < (filter.statements.length - 1) && filter.statements.length > 1">
|
||||
<i class="fa-solid fa-minus" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">{{t('remove-rule')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</app-metadata-row-filter>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
</ng-template>
|
||||
</form>
|
||||
</ng-container>
|
||||
|
||||
</ng-container>
|
||||
|
@ -19,10 +19,9 @@ import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from "@angular
|
||||
import {NgbTooltip} from "@ng-bootstrap/ng-bootstrap";
|
||||
import {FilterCombination} from "../../../_models/metadata/v2/filter-combination";
|
||||
import {FilterUtilitiesService} from "../../../shared/_services/filter-utilities.service";
|
||||
import {FilterComparison} from "../../../_models/metadata/v2/filter-comparison";
|
||||
import {allFields, FilterField} from "../../../_models/metadata/v2/filter-field";
|
||||
import {allFields} from "../../../_models/metadata/v2/filter-field";
|
||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||
import {tap} from "rxjs/operators";
|
||||
import {distinctUntilChanged, tap} from "rxjs/operators";
|
||||
import {translate, TranslocoDirective} from "@ngneat/transloco";
|
||||
|
||||
@Component({
|
||||
@ -52,12 +51,14 @@ export class MetadataBuilderComponent implements OnInit {
|
||||
@Input() statementLimit = 0;
|
||||
@Input() availableFilterFields = allFields;
|
||||
@Output() update: EventEmitter<SeriesFilterV2> = new EventEmitter<SeriesFilterV2>();
|
||||
@Output() apply: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
private readonly cdRef = inject(ChangeDetectorRef);
|
||||
private readonly metadataService = inject(MetadataService);
|
||||
protected readonly utilityService = inject(UtilityService);
|
||||
protected readonly filterUtilityService = inject(FilterUtilitiesService);
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
protected readonly Breakpoint = Breakpoint;
|
||||
|
||||
formGroup: FormGroup = new FormGroup({});
|
||||
|
||||
@ -66,39 +67,30 @@ export class MetadataBuilderComponent implements OnInit {
|
||||
{value: FilterCombination.And, title: translate('metadata-builder.and')},
|
||||
];
|
||||
|
||||
get Breakpoint() { return Breakpoint; }
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
if (this.filter === undefined) {
|
||||
// I've left this in to see if it ever happens or not
|
||||
console.error('No filter, creating one in metadata-builder')
|
||||
// If there is no default preset, let's open with series name
|
||||
this.filter = this.filterUtilityService.createSeriesV2Filter();
|
||||
this.filter.statements.push({
|
||||
value: '',
|
||||
comparison: FilterComparison.Equal,
|
||||
field: FilterField.SeriesName
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[builder] ngOnInit');
|
||||
this.formGroup.addControl('comparison', new FormControl<FilterCombination>(this.filter?.combination || FilterCombination.Or, []));
|
||||
this.formGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef), tap(values => {
|
||||
this.filter.combination = parseInt(this.formGroup.get('comparison')?.value, 10);
|
||||
this.formGroup.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef), tap(values => {
|
||||
this.filter.combination = parseInt(this.formGroup.get('comparison')?.value, 10) as FilterCombination;
|
||||
console.log('[builder] emitting filter from comparison change');
|
||||
this.update.emit(this.filter);
|
||||
})).subscribe()
|
||||
})).subscribe();
|
||||
}
|
||||
|
||||
addFilter() {
|
||||
console.log('[builder] Adding Filter')
|
||||
this.filter.statements = [this.metadataService.createDefaultFilterStatement(), ...this.filter.statements];
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
removeFilter(index: number) {
|
||||
console.log('[builder] Removing filter')
|
||||
this.filter.statements = this.filter.statements.slice(0, index).concat(this.filter.statements.slice(index + 1))
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
updateFilter(index: number, filterStmt: FilterStatement) {
|
||||
console.log('[builder] updating filter: ', this.filter.statements);
|
||||
this.metadataService.updateFilter(this.filter.statements, index, filterStmt);
|
||||
this.update.emit(this.filter);
|
||||
}
|
||||
|
@ -2,11 +2,9 @@
|
||||
<form [formGroup]="formGroup">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-3 me-2 col-10 mb-2">
|
||||
<ng-container *ngIf="formGroup.get('input') as control">
|
||||
<select class="form-select me-2" formControlName="input">
|
||||
<option *ngFor="let field of availableFields" [value]="field">{{field | filterField}}</option>
|
||||
</select>
|
||||
</ng-container>
|
||||
<select class="form-select me-2" formControlName="input">
|
||||
<option *ngFor="let field of availableFields" [value]="field">{{field | filterField}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 me-2 col-10 mb-2">
|
||||
@ -22,7 +20,7 @@
|
||||
<input type="text" class="form-control me-2" autocomplete="true" formControlName="filterValue">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Number">
|
||||
<input type="number" inputmode="numeric" class="form-control me-2" formControlName="filterValue">
|
||||
<input type="number" inputmode="numeric" class="form-control me-2" formControlName="filterValue" min="0">
|
||||
</ng-container>
|
||||
<ng-container *ngSwitchCase="PredicateType.Dropdown">
|
||||
<ng-container *ngIf="dropdownOptions$ | async as opts">
|
||||
|
@ -3,15 +3,23 @@ import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
DestroyRef,
|
||||
EventEmitter,
|
||||
inject,
|
||||
EventEmitter, inject,
|
||||
Input,
|
||||
OnInit,
|
||||
Output
|
||||
Output,
|
||||
} from '@angular/core';
|
||||
import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';
|
||||
import {FilterStatement} from '../../../_models/metadata/v2/filter-statement';
|
||||
import {BehaviorSubject, distinctUntilChanged, map, Observable, of, startWith, switchMap, tap} from 'rxjs';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
distinctUntilChanged, filter,
|
||||
map,
|
||||
Observable,
|
||||
of,
|
||||
startWith,
|
||||
switchMap,
|
||||
tap
|
||||
} from 'rxjs';
|
||||
import {MetadataService} from 'src/app/_services/metadata.service';
|
||||
import {mangaFormatFilters} from 'src/app/_models/metadata/series-filter';
|
||||
import {PersonRole} from 'src/app/_models/metadata/person';
|
||||
@ -32,7 +40,7 @@ enum PredicateType {
|
||||
Dropdown = 3,
|
||||
}
|
||||
|
||||
const StringFields = [FilterField.SeriesName, FilterField.Summary];
|
||||
const StringFields = [FilterField.SeriesName, FilterField.Summary, FilterField.Path, FilterField.FilePath];
|
||||
const NumberFields = [FilterField.ReadTime, FilterField.ReleaseYear, FilterField.ReadProgress, FilterField.UserRating];
|
||||
const DropdownFields = [FilterField.PublicationStatus, FilterField.Languages, FilterField.AgeRating,
|
||||
FilterField.Translators, FilterField.Characters, FilterField.Publisher,
|
||||
@ -81,6 +89,7 @@ const DropdownComparisons = [FilterComparison.Equal,
|
||||
})
|
||||
export class MetadataFilterRowComponent implements OnInit {
|
||||
|
||||
@Input() index: number = 0; // This is only for debugging
|
||||
/**
|
||||
* Slightly misleading as this is the initial state and will be updated on the filterStatement event emitter
|
||||
*/
|
||||
@ -100,9 +109,7 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
dropdownOptions$ = of<Select2Option[]>([]);
|
||||
|
||||
loaded: boolean = false;
|
||||
|
||||
|
||||
get PredicateType() { return PredicateType };
|
||||
protected readonly PredicateType = PredicateType;
|
||||
|
||||
get MultipleDropdownAllowed() {
|
||||
const comp = parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison;
|
||||
@ -113,38 +120,37 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
private readonly collectionTagService: CollectionTagService) {}
|
||||
|
||||
ngOnInit() {
|
||||
console.log('[ngOnInit] creating stmt (' + this.index + '): ', this.preset)
|
||||
this.formGroup.addControl('input', new FormControl<FilterField>(FilterField.SeriesName, []));
|
||||
|
||||
this.formGroup.get('input')?.valueChanges.subscribe((val: string) => this.handleFieldChange(val));
|
||||
this.formGroup.get('input')?.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe((val: string) => this.handleFieldChange(val));
|
||||
this.populateFromPreset();
|
||||
|
||||
this.formGroup.get('filterValue')?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef), tap(v => console.log('filterValue: ', v))).subscribe();
|
||||
|
||||
// Dropdown dynamic option selection
|
||||
this.dropdownOptions$ = this.formGroup.get('input')!.valueChanges.pipe(
|
||||
startWith(this.preset.value),
|
||||
switchMap((_) => this.getDropdownObservable()),
|
||||
tap((opts) => {
|
||||
if (!this.formGroup.get('filterValue')?.value) {
|
||||
this. populateFromPreset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.MultipleDropdownAllowed) {
|
||||
this.formGroup.get('filterValue')?.setValue((opts[0].value + '').split(','));
|
||||
} else {
|
||||
this.formGroup.get('filterValue')?.setValue(opts[0].value);
|
||||
}
|
||||
distinctUntilChanged(),
|
||||
filter(() => {
|
||||
const inputVal = parseInt(this.formGroup.get('input')?.value, 10) as FilterField;
|
||||
return DropdownFields.includes(inputVal);
|
||||
}),
|
||||
switchMap((_) => this.getDropdownObservable()),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
);
|
||||
|
||||
|
||||
this.formGroup.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(_ => {
|
||||
this.filterStatement.emit({
|
||||
this.formGroup!.valueChanges.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)).subscribe(_ => {
|
||||
const stmt = {
|
||||
comparison: parseInt(this.formGroup.get('comparison')?.value, 10) as FilterComparison,
|
||||
field: parseInt(this.formGroup.get('input')?.value, 10) as FilterField,
|
||||
value: this.formGroup.get('filterValue')?.value!
|
||||
});
|
||||
};
|
||||
|
||||
if (!stmt.value && stmt.field !== FilterField.SeriesName) return;
|
||||
console.log('updating parent with new statement: ', stmt.value)
|
||||
this.filterStatement.emit(stmt);
|
||||
});
|
||||
|
||||
this.loaded = true;
|
||||
@ -152,30 +158,37 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
}
|
||||
|
||||
|
||||
|
||||
populateFromPreset() {
|
||||
const val = this.preset.value === "undefined" || !this.preset.value ? '' : this.preset.value;
|
||||
console.log('populating preset: ', val);
|
||||
this.formGroup.get('comparison')?.patchValue(this.preset.comparison);
|
||||
this.formGroup.get('input')?.patchValue(this.preset.field);
|
||||
|
||||
if (StringFields.includes(this.preset.field)) {
|
||||
this.formGroup.get('filterValue')?.patchValue(this.preset.value);
|
||||
this.formGroup.get('filterValue')?.patchValue(val);
|
||||
} else if (DropdownFields.includes(this.preset.field)) {
|
||||
if (this.MultipleDropdownAllowed) {
|
||||
this.formGroup.get('filterValue')?.setValue(this.preset.value.split(','));
|
||||
if (this.MultipleDropdownAllowed || val.includes(',')) {
|
||||
console.log('setting multiple values: ', val.split(',').map(d => parseInt(d, 10)));
|
||||
this.formGroup.get('filterValue')?.patchValue(val.split(',').map(d => parseInt(d, 10)));
|
||||
} else {
|
||||
if (this.preset.field === FilterField.Languages) {
|
||||
this.formGroup.get('filterValue')?.setValue(this.preset.value);
|
||||
this.formGroup.get('filterValue')?.patchValue(val);
|
||||
} else {
|
||||
this.formGroup.get('filterValue')?.setValue(parseInt(this.preset.value, 10));
|
||||
this.formGroup.get('filterValue')?.patchValue(parseInt(val, 10));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.formGroup.get('filterValue')?.patchValue(parseInt(this.preset.value, 10));
|
||||
this.formGroup.get('filterValue')?.patchValue(parseInt(val, 10));
|
||||
}
|
||||
|
||||
this.formGroup.get('comparison')?.patchValue(this.preset.comparison);
|
||||
this.formGroup.get('input')?.setValue(this.preset.field);
|
||||
|
||||
this.cdRef.markForCheck();
|
||||
}
|
||||
|
||||
getDropdownObservable(): Observable<Select2Option[]> {
|
||||
const filterField = parseInt(this.formGroup.get('input')?.value, 10) as FilterField;
|
||||
console.log('Getting dropdown observable: ', filterField);
|
||||
switch (filterField) {
|
||||
case FilterField.PublicationStatus:
|
||||
return this.metadataService.getAllPublicationStatus().pipe(map(pubs => pubs.map(pub => {
|
||||
@ -224,41 +237,46 @@ export class MetadataFilterRowComponent implements OnInit {
|
||||
}
|
||||
|
||||
getPersonOptions(role: PersonRole) {
|
||||
return this.metadataService.getAllPeople().pipe(map(people => people.filter(p2 => p2.role === role).map(person => {
|
||||
return this.metadataService.getAllPeopleByRole(role).pipe(map(people => people.map(person => {
|
||||
return {value: person.id, label: person.name}
|
||||
})))
|
||||
})));
|
||||
}
|
||||
|
||||
|
||||
handleFieldChange(val: string) {
|
||||
const inputVal = parseInt(val, 10) as FilterField;
|
||||
console.log('HandleFieldChange: ', val);
|
||||
|
||||
if (StringFields.includes(inputVal)) {
|
||||
this.validComparisons$.next(StringComparisons);
|
||||
|
||||
this.predicateType$.next(PredicateType.Text);
|
||||
if (this.loaded) this.formGroup.get('filterValue')?.setValue('');
|
||||
|
||||
if (this.loaded) {
|
||||
this.formGroup.get('filterValue')?.patchValue('',{emitEvent: false});
|
||||
console.log('setting filterValue to empty string', this.formGroup.get('filterValue')?.value)
|
||||
} // BUG: undefined is getting set and the input value isn't updating and emitting to the backend
|
||||
return;
|
||||
}
|
||||
|
||||
if (NumberFields.includes(inputVal)) {
|
||||
let comps = [...NumberComparisons];
|
||||
const comps = [...NumberComparisons];
|
||||
if (inputVal === FilterField.ReleaseYear) {
|
||||
comps.push(...DateComparisons);
|
||||
}
|
||||
this.validComparisons$.next(comps);
|
||||
this.predicateType$.next(PredicateType.Number);
|
||||
if (this.loaded) this.formGroup.get('filterValue')?.setValue('');
|
||||
if (this.loaded) this.formGroup.get('filterValue')?.patchValue(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (DropdownFields.includes(inputVal)) {
|
||||
let comps = [...DropdownComparisons];
|
||||
const comps = [...DropdownComparisons];
|
||||
if (inputVal === FilterField.AgeRating) {
|
||||
comps.push(...NumberComparisons);
|
||||
}
|
||||
this.validComparisons$.next(comps);
|
||||
this.predicateType$.next(PredicateType.Dropdown);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,6 +58,10 @@ export class FilterFieldPipe implements PipeTransform {
|
||||
return translate('filter-field-pipe.user-rating');
|
||||
case FilterField.Writers:
|
||||
return translate('filter-field-pipe.writers');
|
||||
case FilterField.Path:
|
||||
return translate('filter-field-pipe.path');
|
||||
case FilterField.FilePath:
|
||||
return translate('filter-field-pipe.file-path');
|
||||
default:
|
||||
throw new Error(`Invalid FilterField value: ${value}`);
|
||||
}
|
||||
|
@ -1,17 +1,18 @@
|
||||
<ng-container *transloco="let t; read: 'metadata-filter'">
|
||||
<ng-container *ngIf="toggleService.toggleState$ | async as isOpen">
|
||||
<div class="phone-hidden">
|
||||
<div class="phone-hidden" *ngIf="utilityService.getActiveBreakpoint() > Breakpoint.Tablet">
|
||||
<div #collapse="ngbCollapse" [ngbCollapse]="!isOpen" (ngbCollapseChange)="setToggle($event)">
|
||||
<ng-container [ngTemplateOutlet]="filterSection"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="not-phone-hidden">
|
||||
<div class="not-phone-hidden" *ngIf="utilityService.getActiveBreakpoint() < Breakpoint.Desktop">
|
||||
<app-drawer #commentDrawer="drawer" [isOpen]="isOpen" [options]="{topOffset: 56}" (drawerClosed)="toggleService.set(false)">
|
||||
<h5 header>
|
||||
{{t('filter-title')}}
|
||||
</h5>
|
||||
<div body class="drawer-body">
|
||||
<!-- TODO: BUG: Filter section is instantiated twice if this isn't ngIf'd -->
|
||||
<ng-container [ngTemplateOutlet]="filterSection"></ng-container>
|
||||
</div>
|
||||
</app-drawer>
|
||||
@ -19,9 +20,13 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-template #filterSection>
|
||||
<div class="filter-section mx-auto pb-3" *ngIf="fullyLoaded">
|
||||
<div class="filter-section mx-auto pb-3" *ngIf="fullyLoaded && filterV2">
|
||||
<div class="row justify-content-center g-0">
|
||||
<app-metadata-builder [filter]="filterV2!" [availableFilterFields]="allFilterFields" (update)="handleFilters($event)" [statementLimit]="filterSettings.statementLimit"></app-metadata-builder>
|
||||
<app-metadata-builder [filter]="filterV2"
|
||||
[availableFilterFields]="allFilterFields"
|
||||
[statementLimit]="filterSettings.statementLimit"
|
||||
(update)="handleFilters($event)">
|
||||
</app-metadata-builder>
|
||||
</div>
|
||||
<form [formGroup]="sortGroup" class="container-fluid">
|
||||
<div class="row mb-3">
|
||||
|
@ -76,6 +76,7 @@ export class MetadataFilterComponent implements OnInit {
|
||||
allFilterFields = allFields;
|
||||
|
||||
handleFilters(filter: SeriesFilterV2) {
|
||||
console.log('[metadata-filter] updating filter');
|
||||
this.filterV2 = filter;
|
||||
}
|
||||
|
||||
@ -86,6 +87,7 @@ export class MetadataFilterComponent implements OnInit {
|
||||
constructor(public toggleService: ToggleService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('[metadata-filter] ngOnInit')
|
||||
if (this.filterSettings === undefined) {
|
||||
this.filterSettings = new FilterSettings();
|
||||
this.cdRef.markForCheck();
|
||||
@ -137,6 +139,7 @@ export class MetadataFilterComponent implements OnInit {
|
||||
loadFromPresetsAndSetup() {
|
||||
this.fullyLoaded = false;
|
||||
|
||||
console.log('[metadata-filter] loading from preset and setting up');
|
||||
this.filterV2 = this.deepClone(this.filterSettings.presetsV2);
|
||||
|
||||
this.sortGroup = new FormGroup({
|
||||
@ -145,6 +148,7 @@ export class MetadataFilterComponent implements OnInit {
|
||||
});
|
||||
|
||||
this.sortGroup.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||
console.log('[metadata-filter] sortGroup value change');
|
||||
if (this.filterV2?.sortOptions === null) {
|
||||
this.filterV2.sortOptions = {
|
||||
isAscending: this.isAscendingSort,
|
||||
@ -152,12 +156,11 @@ export class MetadataFilterComponent implements OnInit {
|
||||
};
|
||||
}
|
||||
this.filterV2!.sortOptions!.sortField = parseInt(this.sortGroup.get('sortField')?.value, 10);
|
||||
this.filterV2!.limitTo = parseInt(this.sortGroup.get('limitTo')?.value, 10);
|
||||
this.filterV2!.limitTo = Math.max(parseInt(this.sortGroup.get('limitTo')?.value || '0', 10), 0);
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
|
||||
this.fullyLoaded = true;
|
||||
this.cdRef.markForCheck();
|
||||
this.apply();
|
||||
}
|
||||
|
||||
@ -173,6 +176,7 @@ export class MetadataFilterComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.filterV2!.sortOptions!.isAscending = this.isAscendingSort;
|
||||
console.log('[metadata-filter] updated filter sort order')
|
||||
}
|
||||
|
||||
clear() {
|
||||
@ -181,7 +185,6 @@ export class MetadataFilterComponent implements OnInit {
|
||||
}
|
||||
|
||||
apply() {
|
||||
|
||||
this.applyFilter.emit({isFirst: this.updateApplied === 0, filterV2: this.filterV2!});
|
||||
|
||||
if (this.utilityService.getActiveBreakpoint() === Breakpoint.Mobile && this.updateApplied !== 0) {
|
||||
|
@ -1731,7 +1731,9 @@
|
||||
"tags": "Tags",
|
||||
"translators": "Translators",
|
||||
"user-rating": "User Rating",
|
||||
"writers": "Writers"
|
||||
"writers": "Writers",
|
||||
"path": "Path",
|
||||
"file-path": "File Path"
|
||||
},
|
||||
|
||||
"filter-comparison-pipe": {
|
||||
|
158
openapi.json
158
openapi.json
@ -3031,6 +3031,69 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/Metadata/people-by-role": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Metadata"
|
||||
],
|
||||
"summary": "Fetches people from the instance by role",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "role",
|
||||
"in": "query",
|
||||
"description": "role",
|
||||
"schema": {
|
||||
"enum": [
|
||||
1,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12
|
||||
],
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/Metadata/people": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -8012,7 +8075,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/Series/all": {
|
||||
"/api/Series/all-v2": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Series"
|
||||
@ -8100,6 +8163,95 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/Series/all": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Series"
|
||||
],
|
||||
"summary": "Returns all series for the library. Obsolete, use all-v2",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "PageNumber",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "PageSize",
|
||||
"in": "query",
|
||||
"description": "If set to 0, will set as MaxInt",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "libraryId",
|
||||
"in": "query",
|
||||
"description": "",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FilterDto"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FilterDto"
|
||||
}
|
||||
},
|
||||
"application/*+json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FilterDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SeriesDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SeriesDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SeriesDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated": true
|
||||
}
|
||||
},
|
||||
"/api/Series/on-deck": {
|
||||
"post": {
|
||||
"tags": [
|
||||
@ -13610,7 +13762,9 @@
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23
|
||||
23,
|
||||
24,
|
||||
25
|
||||
],
|
||||
"type": "integer",
|
||||
"description": "Represents the field which will dictate the value type and the Extension used for filtering",
|
||||
|
Loading…
x
Reference in New Issue
Block a user