From 1f533c6ec18b482e1da4c4f588ca13bafef85c63 Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Sun, 23 Nov 2025 17:14:22 -0500 Subject: [PATCH] Minor search provider enhancement. Minor UI updates. --- .../Common/Queries/SearchProvider.cs | 8 +- .../Circles/CircleSearchProvider.cs | 4 +- .../Creators/CreatorSearchProvider.cs | 4 +- .../Repositories/Tags/TagSearchProvider.cs | 4 +- .../VoiceWorks/VoiceWorkSearchProvider.cs | 13 +- JSMR.UI.Blazor/Pages/VoiceWorks.razor | 192 ++++++++++++++---- JSMR.UI.Blazor/wwwroot/css/app.css | 5 + JSMR.UI.Blazor/wwwroot/css/bit-blazor.css | 29 ++- 8 files changed, 204 insertions(+), 55 deletions(-) diff --git a/JSMR.Infrastructure/Common/Queries/SearchProvider.cs b/JSMR.Infrastructure/Common/Queries/SearchProvider.cs index c34c961..a874fbf 100644 --- a/JSMR.Infrastructure/Common/Queries/SearchProvider.cs +++ b/JSMR.Infrastructure/Common/Queries/SearchProvider.cs @@ -89,13 +89,13 @@ public abstract class SearchProvider : .Take(options.PageSize) .ToArrayAsync(cancellationToken); - Dictionary items = await GetItems(ids); + Dictionary items = await GetItems(options.Criteria, ids); return [.. ids.Select(uniqueId => items[uniqueId])]; } else { - IQueryable selectQuery = GetSelectQuery(orderedQuery); + IQueryable selectQuery = GetSelectQuery(options.Criteria, orderedQuery); return await selectQuery .Skip((options.PageNumber - 1) * options.PageSize) @@ -107,7 +107,7 @@ public abstract class SearchProvider : protected abstract Expression> GetSortExpression(TSortField field); protected abstract IEnumerable<(Expression> Selector, SortDirection Dir)> GetDefaultSortChain(); protected abstract IQueryable GetSelectIdQuery(IOrderedQueryable query); - protected abstract IQueryable GetSelectQuery(IOrderedQueryable query); - protected abstract Task> GetItems(int[] ids); + protected abstract IQueryable GetSelectQuery(TCriteria criteria, IOrderedQueryable query); + protected abstract Task> GetItems(TCriteria criteria, int[] ids); protected virtual Task PostLoadAsync(IList items, CancellationToken cancellationToken) => Task.CompletedTask; } \ No newline at end of file diff --git a/JSMR.Infrastructure/Data/Repositories/Circles/CircleSearchProvider.cs b/JSMR.Infrastructure/Data/Repositories/Circles/CircleSearchProvider.cs index d3a3e1e..8ccf9e6 100644 --- a/JSMR.Infrastructure/Data/Repositories/Circles/CircleSearchProvider.cs +++ b/JSMR.Infrastructure/Data/Repositories/Circles/CircleSearchProvider.cs @@ -141,7 +141,7 @@ public class CircleSearchProvider(AppDbContext context) : SearchProvider x.Circle.CircleId); } - protected override IQueryable GetSelectQuery(IOrderedQueryable query) + protected override IQueryable GetSelectQuery(CircleSearchCriteria criteria, IOrderedQueryable query) { // Join to VoiceWorks by LatestProductId to fill HasImage / SalesDate var selected = @@ -213,7 +213,7 @@ public class CircleSearchProvider(AppDbContext context) : SearchProvider> GetItems(int[] ids) + protected override async Task> GetItems(CircleSearchCriteria criteria, int[] ids) { // Join to VoiceWorks by LatestProductId to fill HasImage / SalesDate var selected = diff --git a/JSMR.Infrastructure/Data/Repositories/Creators/CreatorSearchProvider.cs b/JSMR.Infrastructure/Data/Repositories/Creators/CreatorSearchProvider.cs index 9312e2b..97e9a5d 100644 --- a/JSMR.Infrastructure/Data/Repositories/Creators/CreatorSearchProvider.cs +++ b/JSMR.Infrastructure/Data/Repositories/Creators/CreatorSearchProvider.cs @@ -57,7 +57,7 @@ public class CreatorSearchProvider(AppDbContext context) : SearchProvider x.Name, SortDirection.Ascending); } - protected override IOrderedQueryable GetSelectQuery(IOrderedQueryable query) + protected override IOrderedQueryable GetSelectQuery(CreatorSearchCriteria criteria, IOrderedQueryable query) { return query; } @@ -67,7 +67,7 @@ public class CreatorSearchProvider(AppDbContext context) : SearchProvider> GetItems(int[] ids) + protected override Task> GetItems(CreatorSearchCriteria criteria, int[] ids) { return Task.FromResult(new Dictionary()); } diff --git a/JSMR.Infrastructure/Data/Repositories/Tags/TagSearchProvider.cs b/JSMR.Infrastructure/Data/Repositories/Tags/TagSearchProvider.cs index 09800c7..7a89ba0 100644 --- a/JSMR.Infrastructure/Data/Repositories/Tags/TagSearchProvider.cs +++ b/JSMR.Infrastructure/Data/Repositories/Tags/TagSearchProvider.cs @@ -62,7 +62,7 @@ public class TagSearchProvider(AppDbContext context) : SearchProvider x.Name, SortDirection.Ascending); } - protected override IOrderedQueryable GetSelectQuery(IOrderedQueryable query) + protected override IOrderedQueryable GetSelectQuery(TagSearchCriteria criteria, IOrderedQueryable query) { return query; } @@ -72,7 +72,7 @@ public class TagSearchProvider(AppDbContext context) : SearchProvider> GetItems(int[] ids) + protected override Task> GetItems(TagSearchCriteria criteria, int[] ids) { return Task.FromResult(new Dictionary()); } diff --git a/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs b/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs index 314d90c..e641da0 100644 --- a/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs +++ b/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs @@ -1,4 +1,5 @@ using JSMR.Application.Common.Search; +using JSMR.Application.Enums; using JSMR.Application.VoiceWorks.Queries.Search; using JSMR.Domain.Entities; using JSMR.Domain.Enums; @@ -355,7 +356,7 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea return query.Select(x => x.VoiceWork.VoiceWorkId); } - protected override IQueryable GetSelectQuery(IOrderedQueryable query) + protected override IQueryable GetSelectQuery(VoiceWorkSearchCriteria criteria, IOrderedQueryable query) { var result = from q in query @@ -368,9 +369,9 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea VoiceWorkId = voiceWork.VoiceWorkId, ProductId = voiceWork.ProductId, OriginalProductId = voiceWork.OriginalProductId, - ProductName = englishVoiceWork != null ? englishVoiceWork.ProductName : voiceWork.ProductName, + ProductName = criteria.Locale == Locale.English && englishVoiceWork != null ? englishVoiceWork.ProductName : voiceWork.ProductName, ProductUrl = "http://www.dlsite.com/maniax/" + productLinkPage + "/=/product_id/" + voiceWork.ProductId + ".html", - Description = englishVoiceWork != null ? englishVoiceWork.Description : voiceWork.Description, + Description = criteria.Locale == Locale.English && englishVoiceWork != null ? englishVoiceWork.Description : voiceWork.Description, Favorite = voiceWork.Favorite, HasImage = voiceWork.HasImage, Maker = circle.Name, @@ -390,7 +391,7 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea return result; } - protected override async Task> GetItems(int[] ids) + protected override async Task> GetItems(VoiceWorkSearchCriteria criteria, int[] ids) { var result = from voiceWork in context.VoiceWorks.AsNoTracking() @@ -405,9 +406,9 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea VoiceWorkId = voiceWork.VoiceWorkId, ProductId = voiceWork.ProductId, OriginalProductId = voiceWork.OriginalProductId, - ProductName = englishVoiceWork != null ? englishVoiceWork.ProductName : voiceWork.ProductName, + ProductName = criteria.Locale == Locale.English && englishVoiceWork != null ? englishVoiceWork.ProductName : voiceWork.ProductName, ProductUrl = "http://www.dlsite.com/maniax/" + productLinkPage + "/=/product_id/" + voiceWork.ProductId + ".html", - Description = englishVoiceWork != null ? englishVoiceWork.Description : voiceWork.Description, + Description = criteria.Locale == Locale.English && englishVoiceWork != null ? englishVoiceWork.Description : voiceWork.Description, Favorite = voiceWork.Favorite, HasImage = voiceWork.HasImage, Maker = circle.Name, diff --git a/JSMR.UI.Blazor/Pages/VoiceWorks.razor b/JSMR.UI.Blazor/Pages/VoiceWorks.razor index f745589..0bad9b3 100644 --- a/JSMR.UI.Blazor/Pages/VoiceWorks.razor +++ b/JSMR.UI.Blazor/Pages/VoiceWorks.razor @@ -1,5 +1,8 @@ @page "/voiceworks" @using JSMR.Application.Common.Search +@using JSMR.Application.Creators.Queries.Search +@using JSMR.Application.Enums +@using JSMR.Application.Tags.Queries.Search @using JSMR.Application.VoiceWorks.Queries.Search @using JSMR.Domain.Enums @using JSMR.Domain.ValueObjects @@ -13,16 +16,35 @@

Voice Works

-
+ @*
+
*@ +
+
-
- +
+ +
+
+
-
-
-
-
- + TItem="BitDropdownItem" + TValue="int" + Values="SelectedTagIds" + ValuesChanged="OnTagIdsChanged" />
- +
- + TItem="BitDropdownItem" + TValue="int" + Values="SelectedCreatorIds" + ValuesChanged="OnCreatorIdsChanged" />
- +
@@ -92,16 +124,6 @@
-
- -
+ @*
+ +
*@
@@ -129,27 +154,32 @@ @code { public string? Keywords { get; set; } + public Locale Locale { get; set; } = Locale.English; public SaleStatus? SelectedSaleStatus { get; set; } public CircleStatus? SelectedCircleStatus { get; set; } public TagStatus? SelectedTagStatus { get; set; } public CreatorStatus? SelectedCreatorStatus { get; set; } + public int[] SelectedTagIds { get; set; } = []; + public bool IncludeAllTags { get; set; } + public int[] SelectedCreatorIds { get; set; } = []; + public bool IncludeAllCreators { get; set; } public bool ShowOnlyFavoriteWorks { get; set; } public bool ShowOnlyInvalidWorks { get; set; } public IEnumerable SupportedLanguages { get; set; } = []; public IEnumerable SelectedAgeRatings { get; set; } = []; public DateTimeOffset? ReleaseDateStart { get; set; } public DateTimeOffset? ReleaseDateEnd { get; set; } + public int MinDownloads { get; set; } public int PageNumber { get; set; } = 1; public int PageSize { get; set; } = 100; - IEnumerable> SaleStatuses = + List> Locales = [ - new(SaleStatus.Available, "Available"), - new(SaleStatus.Upcoming, "Upcoming"), - new(null, "All") + new() { Text = "Japanese", Value = Locale.Japanese }, + new() { Text = "English", Value = Locale.English } ]; - List> BasicItems = + List> SaleStatuses = [ new() { Text = "Available", Value = SaleStatus.Available }, new() { Text = "Upcoming", Value = SaleStatus.Upcoming }, @@ -198,11 +228,55 @@ new() { Text = "R18", Value = AgeRating.R18 } ]; + List> Tags = []; + List> Creators = []; + SearchResult? searchResults; protected override async Task OnInitializedAsync() { await UpdateDataAsync(true); + await GetTags(); + await GetCreators(); + } + + private async Task GetTags() + { + SearchTagsRequest request = new( + Options: new() + { + PageNumber = 1, + PageSize = 99999 + } + ); + + SearchTagsResponse? response = await Client.SearchAsync(request); + + if (response is null) + return; + + Tags = response.Results.Items + .OrderByDescending(x => x.Favorite) + .ThenBy(x => x.EnglishName ?? x.Name) + .Select(x => new BitDropdownItem() { Text = x.EnglishName ?? x.Name, Value = x.TagId }).ToList(); + } + + private async Task GetCreators() + { + SearchCreatorsRequest request = new( + Options: new() + { + PageNumber = 1, + PageSize = 99999 + } + ); + + SearchCreatorsResponse? response = await Client.SearchAsync(request); + + if (response is null) + return; + + Creators = response.Results.Items.Select(x => new BitDropdownItem() { Text = x.Name, Value = x.CreatorId }).ToList(); } private async Task UpdateDataAsync(bool resetPageNumber) @@ -218,16 +292,22 @@ Criteria = new() { Keywords = Keywords, + Locale = Locale, SaleStatus = SelectedSaleStatus, CircleStatus = SelectedCircleStatus, TagStatus = SelectedTagStatus, CreatorStatus = SelectedCreatorStatus, + TagIds = [.. SelectedTagIds], + IncludeAllTags = IncludeAllTags, + CreatorIds = [.. SelectedCreatorIds], + IncludeAllCreators = IncludeAllCreators, ShowFavoriteVoiceWorks = ShowOnlyFavoriteWorks, ShowInvalidVoiceWorks = ShowOnlyInvalidWorks, SupportedLanguages = [.. SupportedLanguages], AgeRatings = [.. SelectedAgeRatings], ReleaseDateStart = ReleaseDateStart != null ? DateOnly.FromDateTime(ReleaseDateStart.Value.Date) : null, - ReleaseDateEnd = ReleaseDateEnd != null ? DateOnly.FromDateTime(ReleaseDateEnd.Value.Date) : null + ReleaseDateEnd = ReleaseDateEnd != null ? DateOnly.FromDateTime(ReleaseDateEnd.Value.Date) : null, + //MinDownloads = MinDownloads }, SortOptions = [ @@ -249,6 +329,12 @@ await UpdateDataAsync(true); } + public async Task OnLocaleChanged(Locale locale) + { + Locale = locale; + await UpdateDataAsync(true); + } + public async Task OnSaleStatusChanged(SaleStatus? saleStatus) { SelectedSaleStatus = saleStatus; @@ -273,6 +359,30 @@ await UpdateDataAsync(true); } + public async Task OnTagIdsChanged(IEnumerable tagIds) + { + SelectedTagIds = [..tagIds]; + await UpdateDataAsync(true); + } + + public async Task OnIncludeAllTagsChanged(bool includeAllTags) + { + IncludeAllTags = includeAllTags; + await UpdateDataAsync(true); + } + + public async Task OnCreatorIdsChanged(IEnumerable creatorIds) + { + SelectedCreatorIds = [..creatorIds]; + await UpdateDataAsync(true); + } + + public async Task OnIncludeAllCreatorsChanged(bool includeAllCreators) + { + IncludeAllCreators = includeAllCreators; + await UpdateDataAsync(true); + } + public async Task OnSupportedLanguagesChanged(IEnumerable languages) { SupportedLanguages = languages; @@ -309,6 +419,12 @@ await UpdateDataAsync(true); } + public async Task OnMinDownloadsChanged(double minDownloads) + { + MinDownloads = (int)minDownloads; + await UpdateDataAsync(true); + } + public async Task OnPageNumberChanged(int newPageNumber) { PageNumber = newPageNumber; diff --git a/JSMR.UI.Blazor/wwwroot/css/app.css b/JSMR.UI.Blazor/wwwroot/css/app.css index 2cda92b..af3a8f0 100644 --- a/JSMR.UI.Blazor/wwwroot/css/app.css +++ b/JSMR.UI.Blazor/wwwroot/css/app.css @@ -132,6 +132,11 @@ code { align-items: center; } + .search-filter-control-container > .search-filter-control-span-1 { + display: flex; + align-items: center; + } + .search-filter-control-container > .search-filter-control-span-2 { display: flex; align-items: center; diff --git a/JSMR.UI.Blazor/wwwroot/css/bit-blazor.css b/JSMR.UI.Blazor/wwwroot/css/bit-blazor.css index a1cf88a..63843d4 100644 --- a/JSMR.UI.Blazor/wwwroot/css/bit-blazor.css +++ b/JSMR.UI.Blazor/wwwroot/css/bit-blazor.css @@ -4,7 +4,7 @@ border: 1px solid var(--input-border-color); background-color: var(--input-background-color); transition: background-color .2s,color .2s,border-color .2s,box-shadow .2s; - padding: .25rem .5rem; + /*padding: .25rem .5rem;*/ } .bit-tfl-fgp { @@ -25,6 +25,14 @@ } /* DropDownList */ +.bit-drp { + font-family: var(--font-family); +} + +.bit-drp-lbl { + font-weight: 500; +} + .bit-drp-cal { background-color: var(--input-background-color); } @@ -38,4 +46,23 @@ .bit-drp-sel:hover { color: #ffffffde; background: rgba(100,181,246,.16); +} + +.bit-drp-scn { + max-height: 200px !important; + padding: .5rem 0; +} + +.bit-drp-itm, +.bit-drp-mcn { + justify-content: flex-start; /* To get around Mud Blazor, until Mud Blazor is removed */ + padding: .5rem 1rem; +} + +.bit-drp-sch .bit-drp-tcn { + padding: 0; +} + +.bit-drp-pre, .bit-drp-suf { + background: rgb(57, 79, 94); } \ No newline at end of file