diff --git a/JSMR.UI.Blazor/Components/VoiceWorkFilters.razor b/JSMR.UI.Blazor/Components/VoiceWorkFilters.razor
new file mode 100644
index 0000000..8611fb9
--- /dev/null
+++ b/JSMR.UI.Blazor/Components/VoiceWorkFilters.razor
@@ -0,0 +1,208 @@
+@using JSMR.Application.Enums
+@using JSMR.Application.VoiceWorks.Queries.Search
+@using JSMR.Domain.Enums
+@using JSMR.Domain.ValueObjects
+@using JSMR.UI.Blazor.Enums
+@using JSMR.UI.Blazor.Filters
+@using JSMR.UI.Blazor.Services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @*
+
+
*@
+
+
+@code {
+ [Inject]
+ ILookupDataService Lookups { get; set; } = default!;
+
+ [Parameter]
+ public VoiceWorkFilterState Value { get; set; } = new();
+
+ [Parameter]
+ public EventCallback ValueChanged { get; set; }
+
+ List> locales = [];
+ List> saleStatuses = [];
+ List> circleStatuses = [];
+ List> tagStatuses = [];
+ List> creatorStatuses = [];
+ List> languages = [];
+ List> ageRatings = [];
+ List> sortOptions = [];
+ List> tags = [];
+ List> creators = [];
+
+ protected override async Task OnInitializedAsync()
+ {
+ locales = Lookups.GetLocales();
+ saleStatuses = Lookups.GetSaleStatuses();
+ circleStatuses = Lookups.GetCircleStatuses();
+ tagStatuses = Lookups.GetTagStatuses();
+ creatorStatuses = Lookups.GetCreatorStatuses();
+ languages = Lookups.GetLanguages();
+ ageRatings = Lookups.GetAgeRatings();
+ sortOptions = Lookups.GetSortOptions();
+
+ (tags, creators) = (await Lookups.GetTagsAsync(), await Lookups.GetCreatorsAsync());
+ }
+
+ private Task Update(VoiceWorkFilterState next)
+ => ValueChanged.InvokeAsync(next with { PageNumber = 1 });
+
+ // Map DateOnly? -> DateTimeOffset? (UTC midnight to avoid TZ drift)
+ private static DateTimeOffset? ToDto(DateOnly? d) =>
+ d is null ? null
+ : new DateTimeOffset(d.Value.ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified), TimeSpan.Zero);
+
+ // Map DateTimeOffset? -> DateOnly?
+ private static DateOnly? FromDto(DateTimeOffset? dto) =>
+ dto is null ? null : DateOnly.FromDateTime(dto.Value.Date);
+}
\ No newline at end of file
diff --git a/JSMR.UI.Blazor/Enums/VoiceWorkSort.cs b/JSMR.UI.Blazor/Enums/VoiceWorkSort.cs
new file mode 100644
index 0000000..007f959
--- /dev/null
+++ b/JSMR.UI.Blazor/Enums/VoiceWorkSort.cs
@@ -0,0 +1,9 @@
+namespace JSMR.UI.Blazor.Enums;
+
+public enum VoiceWorkSort
+{
+ ReleaseDateNewToOld,
+ ReleaseDateOldToNew,
+ BestSelling,
+ MostFavorited
+}
diff --git a/JSMR.UI.Blazor/Filters/VoiceWorkFilterState.cs b/JSMR.UI.Blazor/Filters/VoiceWorkFilterState.cs
new file mode 100644
index 0000000..a75873e
--- /dev/null
+++ b/JSMR.UI.Blazor/Filters/VoiceWorkFilterState.cs
@@ -0,0 +1,233 @@
+using JSMR.Application.Common.Search;
+using JSMR.Application.Enums;
+using JSMR.Application.VoiceWorks.Queries.Search;
+using JSMR.Domain.Enums;
+using JSMR.UI.Blazor.Enums;
+using Microsoft.AspNetCore.WebUtilities;
+using System.Globalization;
+
+namespace JSMR.UI.Blazor.Filters;
+
+public sealed record VoiceWorkFilterState
+{
+ // Core
+ public string? Keywords { get; init; }
+ public Locale Locale { get; init; } = Locale.English;
+
+ // Statuses
+ public SaleStatus? SaleStatus { get; init; }
+ public CircleStatus? CircleStatus { get; init; }
+ public TagStatus? TagStatus { get; init; }
+ public CreatorStatus? CreatorStatus { get; init; }
+
+ // Multi-selects
+ public int[] TagIds { get; init; } = Array.Empty();
+ public bool IncludeAllTags { get; init; }
+ public int[] CreatorIds { get; init; } = Array.Empty();
+ public bool IncludeAllCreators { get; init; }
+ public Language[] SupportedLanguages { get; init; } = Array.Empty();
+ public AgeRating[] AgeRatings { get; init; } = Array.Empty();
+
+ // Misc flags
+ public bool ShowFavorite { get; init; }
+ public bool ShowInvalid { get; init; }
+
+ // Dates (use yyyy-MM-dd in URL)
+ public DateOnly? ReleaseDateStart { get; init; }
+ public DateOnly? ReleaseDateEnd { get; init; }
+
+ // Sorting & paging
+ public VoiceWorkSort Sort { get; init; } = VoiceWorkSort.ReleaseDateNewToOld;
+ public int PageNumber { get; init; } = 1;
+ public int PageSize { get; init; } = 100;
+
+ // ---- Query <-> State helpers ----
+ public Dictionary ToQuery()
+ {
+ Dictionary query = [];
+
+ void Set(string key, string? value, bool includeWhenEmpty = false)
+ {
+ if (includeWhenEmpty || !string.IsNullOrWhiteSpace(value))
+ query[key] = value;
+ }
+
+ string Join(IEnumerable xs) => string.Join(",", xs);
+
+ Set("keywords", Keywords);
+
+ if (Locale != Locale.English)
+ Set("locale", Locale.ToString());
+
+ if (SaleStatus is not null)
+ Set("sale", SaleStatus.ToString());
+
+ if (CircleStatus is not null)
+ Set("circle", CircleStatus.ToString());
+
+ if (TagStatus is not null)
+ Set("tag", TagStatus.ToString());
+
+ if (CreatorStatus is not null)
+ Set("creator", CreatorStatus.ToString());
+
+ if (TagIds.Length > 0)
+ Set("tagIds", Join(TagIds));
+
+ if (IncludeAllTags)
+ Set("allTags", "1");
+
+ if (CreatorIds.Length > 0)
+ Set("creatorIds", Join(CreatorIds));
+
+ if (IncludeAllCreators)
+ Set("allCreators", "1");
+
+ if (SupportedLanguages.Length > 0)
+ Set("langs", Join(SupportedLanguages));
+
+ if (AgeRatings.Length > 0)
+ Set("ages", Join(AgeRatings));
+
+ if (ShowFavorite)
+ Set("fav", "1");
+
+ if (ShowInvalid)
+ Set("invalid", "1");
+
+ if (ReleaseDateStart is not null)
+ Set("rs", ReleaseDateStart.Value.ToString("yyyy-MM-dd"));
+
+ if (ReleaseDateEnd is not null)
+ Set("re", ReleaseDateEnd.Value.ToString("yyyy-MM-dd"));
+
+ if (Sort != VoiceWorkSort.ReleaseDateNewToOld)
+ Set("sort", Sort.ToString());
+
+ if (PageNumber != 1)
+ Set("pageNumber", PageNumber.ToString(CultureInfo.InvariantCulture));
+
+ if (PageSize != 100)
+ Set("pageSize", PageSize.ToString(CultureInfo.InvariantCulture));
+
+ return query;
+ }
+
+ public static VoiceWorkFilterState FromQuery(string query)
+ {
+ var dict = QueryHelpers.ParseQuery(query);
+
+ T? GetEnum(string key) where T : struct, Enum
+ => dict.TryGetValue(key, out var v) && Enum.TryParse(v!, true, out var e) ? e : null;
+
+ string? Get(string key) => dict.TryGetValue(key, out var v) ? v!.ToString() : null;
+ int[] GetInts(string key) => Get(key)?.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray() ?? Array.Empty();
+ TEnum[] GetEnums(string key) where TEnum : struct, Enum
+ => Get(key)?.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => Enum.TryParse(s, true, out var e) ? e : (TEnum?)null).Where(e => e is not null).Select(e => e!.Value).ToArray()
+ ?? Array.Empty();
+ DateOnly? GetDate(string key) => DateOnly.TryParseExact(Get(key), "yyyy-MM-dd", out var d) ? d : null;
+ bool Has(string key) => dict.ContainsKey(key);
+
+ return new VoiceWorkFilterState
+ {
+ Keywords = Get("keywords"),
+ Locale = GetEnum("locale") ?? Locale.English,
+
+ SaleStatus = GetEnum("sale"),
+ CircleStatus = GetEnum("circle"),
+ TagStatus = GetEnum("tag"),
+ CreatorStatus = GetEnum("creator"),
+
+ TagIds = GetInts("tagIds"),
+ IncludeAllTags = Has("allTags"),
+ CreatorIds = GetInts("creatorIds"),
+ IncludeAllCreators = Has("allCreators"),
+ SupportedLanguages = GetEnums("langs"),
+ AgeRatings = GetEnums("ages"),
+
+ ShowFavorite = Has("fav"),
+ ShowInvalid = Has("invalid"),
+
+ ReleaseDateStart = GetDate("rs"),
+ ReleaseDateEnd = GetDate("re"),
+
+ Sort = GetEnum("sort") ?? VoiceWorkSort.ReleaseDateNewToOld,
+ PageNumber = int.TryParse(Get("pageNumber"), out var pageNumber) ? Math.Max(1, pageNumber) : 1,
+ PageSize = int.TryParse(Get("pageSize"), out var pageSize) ? pageSize : 100
+ };
+ }
+
+ public SearchVoiceWorksRequest ToSearchRequest()
+ {
+ return new(
+ Options: new()
+ {
+ Criteria = new()
+ {
+ Keywords = Keywords,
+ Locale = Locale,
+ SaleStatus = SaleStatus,
+ CircleStatus = CircleStatus,
+ TagStatus = TagStatus,
+ CreatorStatus = CreatorStatus,
+ TagIds = TagIds,
+ IncludeAllTags = IncludeAllTags,
+ CreatorIds = CreatorIds,
+ IncludeAllCreators = IncludeAllCreators,
+ ShowFavoriteVoiceWorks = ShowFavorite,
+ ShowInvalidVoiceWorks = ShowInvalid,
+ SupportedLanguages = SupportedLanguages,
+ AgeRatings = AgeRatings,
+ ReleaseDateStart = ReleaseDateStart,
+ ReleaseDateEnd = ReleaseDateEnd,
+ //MinDownloads = MinDownloads
+ },
+ SortOptions = GetSortOptions(),
+ PageNumber = PageNumber,
+ PageSize = PageSize
+ }
+ );
+ }
+
+ private SortOption[] GetSortOptions()
+ {
+ switch (Sort)
+ {
+ case VoiceWorkSort.ReleaseDateNewToOld:
+ return
+ [
+ new(GetReleaseDateVoiceWorkSortField(), SortDirection.Descending)
+ ];
+ case VoiceWorkSort.ReleaseDateOldToNew:
+ return
+ [
+ new(GetReleaseDateVoiceWorkSortField(), SortDirection.Ascending)
+ ];
+ case VoiceWorkSort.BestSelling:
+ return
+ [
+ new(VoiceWorkSortField.Downloads, SortDirection.Descending)
+ ];
+ case VoiceWorkSort.MostFavorited:
+ return
+ [
+ new(VoiceWorkSortField.WishlistCount, SortDirection.Descending)
+ ];
+ }
+
+ return [];
+ }
+
+ private VoiceWorkSortField GetReleaseDateVoiceWorkSortField()
+ {
+ switch (SaleStatus)
+ {
+ case Application.VoiceWorks.Queries.Search.SaleStatus.Available:
+ return VoiceWorkSortField.ReleaseDate;
+ case Application.VoiceWorks.Queries.Search.SaleStatus.Upcoming:
+ return VoiceWorkSortField.ExpectedReleaseDate;
+ default:
+ return VoiceWorkSortField.AnyReleaseDate;
+ }
+ }
+}
\ No newline at end of file
diff --git a/JSMR.UI.Blazor/Pages/VoiceWorks.razor b/JSMR.UI.Blazor/Pages/VoiceWorks.razor
index 0bad9b3..1bbb102 100644
--- a/JSMR.UI.Blazor/Pages/VoiceWorks.razor
+++ b/JSMR.UI.Blazor/Pages/VoiceWorks.razor
@@ -1,452 +1,114 @@
@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
@using JSMR.UI.Blazor.Components
+@using JSMR.UI.Blazor.Filters
@using JSMR.UI.Blazor.Services
+@using Microsoft.AspNetCore.WebUtilities
@inject VoiceWorksClient Client
@inject IJSRuntime JS
+@inject NavigationManager NavigationManager
+@implements IAsyncDisposable;
Voice Works
Voice Works
-
- @*
-
-
*@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- @*
-
-
*@
-
+
@if (searchResults is not null)
{
-
+
}
@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;
-
- List> Locales =
- [
- new() { Text = "Japanese", Value = Locale.Japanese },
- new() { Text = "English", Value = Locale.English }
- ];
-
- List> SaleStatuses =
- [
- new() { Text = "Available", Value = SaleStatus.Available },
- new() { Text = "Upcoming", Value = SaleStatus.Upcoming },
- new() { Text = "All", Value = null }
- ];
-
- List> CircleStatuses =
- [
- new() { Text = "Not Blacklisted", Value = CircleStatus.NotBlacklisted },
- new() { Text = "Favorites", Value = CircleStatus.Favorited },
- new() { Text = "Blacklisted", Value = CircleStatus.Blacklisted },
- new() { Text = "All", Value = null }
- ];
-
- List> TagStatuses =
- [
- new() { Text = "Not Blacklisted", Value = TagStatus.NotBlacklisted },
- new() { Text = "Favorites (Exclude Blacklisted)", Value = TagStatus.FavoriteExcludeBlacklist },
- new() { Text = "Favorites (Include Blacklisted)", Value = TagStatus.FavoriteIncludeBlacklist },
- new() { Text = "Blacklisted", Value = TagStatus.Blacklisted },
- new() { Text = "All", Value = null }
- ];
-
- List> CreatorStatuses =
- [
- new() { Text = "Not Blacklisted", Value = CreatorStatus.NotBlacklisted },
- new() { Text = "Favorites (Exclude Blacklisted)", Value = CreatorStatus.FavoriteExcludeBlacklist },
- new() { Text = "Favorites (Include Blacklisted)", Value = CreatorStatus.FavoriteIncludeBlacklist },
- new() { Text = "Blacklisted", Value = CreatorStatus.Blacklisted },
- new() { Text = "All", Value = null }
- ];
-
- List> Languages =
- [
- new() { Text = "Japanese", Value = Language.Japanese },
- new() { Text = "English", Value = Language.English },
- new() { Text = "Chinese (Traditional)", Value = Language.ChineseTraditional },
- new() { Text = "Chinese (Simplified)", Value = Language.ChineseSimplified },
- new() { Text = "Korean", Value = Language.Korean }
- ];
-
- List> AgeRatings =
- [
- new() { Text = "All Ages", Value = AgeRating.AllAges },
- new() { Text = "R15", Value = AgeRating.R15 },
- new() { Text = "R18", Value = AgeRating.R18 }
- ];
-
- List> Tags = [];
- List> Creators = [];
+ private bool _isAlive = true;
+ private CancellationTokenSource _cts = new();
+ VoiceWorkFilterState FilterState = new();
SearchResult? searchResults;
protected override async Task OnInitializedAsync()
{
- await UpdateDataAsync(true);
- await GetTags();
- await GetCreators();
+ NavigationManager.LocationChanged += OnLocationChanged;
+
+ FilterState = VoiceWorkFilterState.FromQuery(new Uri(NavigationManager.Uri).Query);
+ await RunSearchAsync();
}
- private async Task GetTags()
+ private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
- SearchTagsRequest request = new(
- Options: new()
- {
- PageNumber = 1,
- PageSize = 99999
- }
- );
-
- SearchTagsResponse? response = await Client.SearchAsync(request);
-
- if (response is null)
+ if (!_isAlive)
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)
+ if (!IsThisPage(e.Location))
return;
- Creators = response.Results.Items.Select(x => new BitDropdownItem() { Text = x.Name, Value = x.CreatorId }).ToList();
+ // Parse query from the new URL and update state if it actually changed.
+ string query = NavigationManager.ToAbsoluteUri(e.Location).Query;
+ VoiceWorkFilterState next = VoiceWorkFilterState.FromQuery(query);
+
+ if (next != FilterState)
+ {
+ FilterState = next;
+ await RunSearchAsync();
+ await InvokeAsync(StateHasChanged);
+ }
}
- private async Task UpdateDataAsync(bool resetPageNumber)
+ private bool IsThisPage(string absoluteUri)
+ {
+ string baseRelativePath = NavigationManager.ToBaseRelativePath(absoluteUri);
+
+ return baseRelativePath.StartsWith("voiceworks", StringComparison.OrdinalIgnoreCase);
+ }
+
+ async Task OnFilterStateChanged(VoiceWorkFilterState next)
+ {
+ if (next == FilterState)
+ return;
+
+ UpdateUrl(next, false);
+ }
+
+ void UpdateUrl(VoiceWorkFilterState next, bool replace)
+ {
+ string basePath = new Uri(NavigationManager.Uri).GetLeftPart(UriPartial.Path);
+ string uri = QueryHelpers.AddQueryString(basePath, next.ToQuery());
+
+ NavigationManager.NavigateTo(uri, replace: replace);
+ }
+
+ async Task RunSearchAsync()
{
await JS.InvokeVoidAsync("pageHelpers.scrollToTop");
- if (resetPageNumber)
- PageNumber = 1;
-
- SearchVoiceWorksRequest request = new(
- Options: new()
- {
- 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,
- //MinDownloads = MinDownloads
- },
- SortOptions =
- [
- new(GetSortField(), Application.Common.Search.SortDirection.Descending)
- ],
- PageNumber = PageNumber,
- PageSize = PageSize
- }
- );
-
- SearchVoiceWorksResponse? response = await Client.SearchAsync(request);
-
- searchResults = response?.Results;
- }
-
- public async Task OnKeywordsChanged(string? newKeywords)
- {
- Keywords = newKeywords;
- await UpdateDataAsync(true);
- }
-
- public async Task OnLocaleChanged(Locale locale)
- {
- Locale = locale;
- await UpdateDataAsync(true);
- }
-
- public async Task OnSaleStatusChanged(SaleStatus? saleStatus)
- {
- SelectedSaleStatus = saleStatus;
- await UpdateDataAsync(true);
- }
-
- public async Task OnCircleStatusChanged(CircleStatus? circleStatus)
- {
- SelectedCircleStatus = circleStatus;
- await UpdateDataAsync(true);
- }
-
- public async Task OnTagStatusChanged(TagStatus? tagStatus)
- {
- SelectedTagStatus = tagStatus;
- await UpdateDataAsync(true);
- }
-
- public async Task OnCreatorStatusChanged(CreatorStatus? creatorStatus)
- {
- SelectedCreatorStatus = creatorStatus;
- 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;
- await UpdateDataAsync(true);
- }
-
- public async Task OnAgeRatingsChanged(IEnumerable ageRatings)
- {
- SelectedAgeRatings = ageRatings;
- await UpdateDataAsync(true);
- }
-
- public async Task OnShowOnlyFavoriteWorksChanged(bool showOnlyFavoriteWorks)
- {
- ShowOnlyFavoriteWorks = showOnlyFavoriteWorks;
- await UpdateDataAsync(true);
- }
-
- public async Task OnShowOnlyInvalidWorksChanged(bool showOnlyInvalidWorks)
- {
- ShowOnlyInvalidWorks = showOnlyInvalidWorks;
- await UpdateDataAsync(true);
- }
-
- public async Task OnReleaseDateStartChanged(DateTimeOffset? releaseDateStart)
- {
- ReleaseDateStart = releaseDateStart;
- await UpdateDataAsync(true);
- }
-
- public async Task OnReleaseDateEndChanged(DateTimeOffset? releaseDateEnd)
- {
- ReleaseDateEnd = releaseDateEnd;
- await UpdateDataAsync(true);
- }
-
- public async Task OnMinDownloadsChanged(double minDownloads)
- {
- MinDownloads = (int)minDownloads;
- await UpdateDataAsync(true);
- }
-
- public async Task OnPageNumberChanged(int newPageNumber)
- {
- PageNumber = newPageNumber;
- await UpdateDataAsync(false);
- }
-
- public async Task OnPageSizeChanged(int newPageSize)
- {
- PageSize = newPageSize;
- await UpdateDataAsync(true);
- }
-
- private VoiceWorkSortField GetSortField()
- {
- switch (SelectedSaleStatus)
+ try
{
- case SaleStatus.Available:
- return VoiceWorkSortField.ReleaseDate;
- case SaleStatus.Upcoming:
- return VoiceWorkSortField.ExpectedReleaseDate;
- default:
- return VoiceWorkSortField.AnyReleaseDate;
+ _cts.Cancel();
+ _cts = new();
+
+ SearchVoiceWorksResponse? response = await Client.SearchAsync(FilterState.ToSearchRequest(), _cts.Token);
+ searchResults = response?.Results;
+ }
+ catch (OperationCanceledException)
+ {
+
}
}
+
+ public async ValueTask DisposeAsync()
+ {
+ _isAlive = false;
+ NavigationManager.LocationChanged -= OnLocationChanged;
+ _cts.Cancel();
+ _cts.Dispose();
+ await Task.CompletedTask;
+ }
}
\ No newline at end of file
diff --git a/JSMR.UI.Blazor/Program.cs b/JSMR.UI.Blazor/Program.cs
index 4ac2619..0b88936 100644
--- a/JSMR.UI.Blazor/Program.cs
+++ b/JSMR.UI.Blazor/Program.cs
@@ -20,5 +20,6 @@ builder.Services.AddRadzenComponents();
builder.Services.AddBitBlazorUIServices();
builder.Services.AddScoped();
+builder.Services.AddScoped();
await builder.Build().RunAsync();
\ No newline at end of file
diff --git a/JSMR.UI.Blazor/Services/ILookupDataService.cs b/JSMR.UI.Blazor/Services/ILookupDataService.cs
new file mode 100644
index 0000000..48b52dc
--- /dev/null
+++ b/JSMR.UI.Blazor/Services/ILookupDataService.cs
@@ -0,0 +1,22 @@
+using Bit.BlazorUI;
+using JSMR.Application.Enums;
+using JSMR.Application.VoiceWorks.Queries.Search;
+using JSMR.Domain.Enums;
+using JSMR.UI.Blazor.Enums;
+
+namespace JSMR.UI.Blazor.Services;
+
+public interface ILookupDataService
+{
+ List> GetLocales();
+ List> GetSaleStatuses();
+ List> GetCircleStatuses();
+ List> GetTagStatuses();
+ List> GetCreatorStatuses();
+ List> GetLanguages();
+ List> GetAgeRatings();
+ List> GetSortOptions();
+
+ Task>> GetTagsAsync(CancellationToken cancellationToken = default);
+ Task>> GetCreatorsAsync(CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/JSMR.UI.Blazor/Services/LookupDataService.cs b/JSMR.UI.Blazor/Services/LookupDataService.cs
new file mode 100644
index 0000000..7c86049
--- /dev/null
+++ b/JSMR.UI.Blazor/Services/LookupDataService.cs
@@ -0,0 +1,134 @@
+using Bit.BlazorUI;
+using JSMR.Application.Creators.Queries.Search;
+using JSMR.Application.Creators.Queries.Search.Contracts;
+using JSMR.Application.Enums;
+using JSMR.Application.Tags.Queries.Search;
+using JSMR.Application.Tags.Queries.Search.Contracts;
+using JSMR.Application.VoiceWorks.Queries.Search;
+using JSMR.Domain.Enums;
+using JSMR.UI.Blazor.Enums;
+
+namespace JSMR.UI.Blazor.Services;
+
+public sealed class LookupDataService(VoiceWorksClient client) : ILookupDataService
+{
+ // simple in-memory caches for WASM
+ private List>? _tags;
+ private List>? _creators;
+
+ public List> GetLocales() =>
+ [
+ new() { Text = "Japanese", Value = Locale.Japanese },
+ new() { Text = "English", Value = Locale.English }
+ ];
+
+ public List> GetSaleStatuses() =>
+ [
+ new() { Text = "Available", Value = SaleStatus.Available },
+ new() { Text = "Upcoming", Value = SaleStatus.Upcoming },
+ new() { Text = "All", Value = null }
+ ];
+
+ public List> GetCircleStatuses() =>
+ [
+ new() { Text = "Not Blacklisted", Value = CircleStatus.NotBlacklisted },
+ new() { Text = "Favorites", Value = CircleStatus.Favorited },
+ new() { Text = "Blacklisted", Value = CircleStatus.Blacklisted },
+ new() { Text = "All", Value = null }
+ ];
+
+ public List> GetTagStatuses() =>
+ [
+ new() { Text = "Not Blacklisted", Value = TagStatus.NotBlacklisted },
+ new() { Text = "Favorites (Exclude Blacklisted)", Value = TagStatus.FavoriteExcludeBlacklist },
+ new() { Text = "Favorites (Include Blacklisted)", Value = TagStatus.FavoriteIncludeBlacklist },
+ new() { Text = "Blacklisted", Value = TagStatus.Blacklisted },
+ new() { Text = "All", Value = null }
+ ];
+
+ public List> GetCreatorStatuses() =>
+ [
+ new() { Text = "Not Blacklisted", Value = CreatorStatus.NotBlacklisted },
+ new() { Text = "Favorites (Exclude Blacklisted)", Value = CreatorStatus.FavoriteExcludeBlacklist },
+ new() { Text = "Favorites (Include Blacklisted)", Value = CreatorStatus.FavoriteIncludeBlacklist },
+ new() { Text = "Blacklisted", Value = CreatorStatus.Blacklisted },
+ new() { Text = "All", Value = null }
+ ];
+
+ public List> GetLanguages() =>
+ [
+ new() { Text = "Japanese", Value = Language.Japanese },
+ new() { Text = "English", Value = Language.English },
+ new() { Text = "Chinese (Traditional)",Value = Language.ChineseTraditional },
+ new() { Text = "Chinese (Simplified)", Value = Language.ChineseSimplified },
+ new() { Text = "Korean", Value = Language.Korean }
+ ];
+
+ public List> GetAgeRatings() =>
+ [
+ new() { Text = "All Ages", Value = AgeRating.AllAges },
+ new() { Text = "R15", Value = AgeRating.R15 },
+ new() { Text = "R18", Value = AgeRating.R18 }
+ ];
+
+ public List> GetSortOptions() =>
+ [
+ new()
+ {
+ Text = "Release Date - New to Old",
+ Value = VoiceWorkSort.ReleaseDateNewToOld
+ },
+ new()
+ {
+ Text = "Release Date - Old to New",
+ Value = VoiceWorkSort.ReleaseDateOldToNew
+ },
+ new()
+ {
+ Text = "Best Selling",
+ Value = VoiceWorkSort.BestSelling
+ },
+ new()
+ {
+ Text = "Most Favorited",
+ Value = VoiceWorkSort.MostFavorited
+ }
+ ];
+
+ public async Task>> GetTagsAsync(CancellationToken ct = default)
+ {
+ if (_tags is not null) return _tags;
+
+ var resp = await client.SearchAsync(new SearchTagsRequest(new()
+ {
+ PageNumber = 1,
+ PageSize = 99999
+ }), ct);
+
+ _tags = (resp?.Results.Items ?? Array.Empty())
+ .OrderByDescending(x => x.Favorite)
+ .ThenBy(x => x.EnglishName ?? x.Name, StringComparer.Ordinal)
+ .Select(x => new BitDropdownItem { Text = x.EnglishName ?? x.Name, Value = x.TagId })
+ .ToList();
+
+ return _tags;
+ }
+
+ public async Task>> GetCreatorsAsync(CancellationToken ct = default)
+ {
+ if (_creators is not null) return _creators;
+
+ var resp = await client.SearchAsync(new SearchCreatorsRequest(new()
+ {
+ PageNumber = 1,
+ PageSize = 99999
+ }), ct);
+
+ _creators = (resp?.Results.Items ?? Array.Empty())
+ .OrderBy(x => x.Name, StringComparer.Ordinal)
+ .Select(x => new BitDropdownItem { Text = x.Name, Value = x.CreatorId })
+ .ToList();
+
+ return _creators;
+ }
+}
\ No newline at end of file