Simplified and enhnaced voice work page.
This commit is contained in:
208
JSMR.UI.Blazor/Components/VoiceWorkFilters.razor
Normal file
208
JSMR.UI.Blazor/Components/VoiceWorkFilters.razor
Normal file
@@ -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
|
||||||
|
|
||||||
|
<div class="search-filter-control-container">
|
||||||
|
<div class="search-filter-control-span-2">
|
||||||
|
<BitTextField Prefix="Keywords"
|
||||||
|
Immediate="true" DebounceTime="500"
|
||||||
|
Value="@Value.Keywords"
|
||||||
|
ValueChanged="@(value => Update(Value with { Keywords = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDropdown Prefix="Languages"
|
||||||
|
MultiSelect
|
||||||
|
Items="languages"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<Language>"
|
||||||
|
TValue="Language"
|
||||||
|
Values="@Value.SupportedLanguages"
|
||||||
|
ValuesChanged="@(values => Update(Value with { SupportedLanguages = [.. values] }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDropdown Prefix="Locale"
|
||||||
|
Items="locales"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<Locale>"
|
||||||
|
TValue="Locale"
|
||||||
|
Value="@Value.Locale"
|
||||||
|
ValueChanged="@(value => Update(Value with { Locale = value }))" />
|
||||||
|
</div>
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDropdown Prefix="Sale Status"
|
||||||
|
Items="saleStatuses"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<SaleStatus?>"
|
||||||
|
TValue="SaleStatus ?"
|
||||||
|
Value="@Value.SaleStatus"
|
||||||
|
ValueChanged="@(value => Update(Value with { SaleStatus = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDropdown Prefix="Circles"
|
||||||
|
Items="circleStatuses"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<CircleStatus?>"
|
||||||
|
TValue="CircleStatus ?"
|
||||||
|
Value="@Value.CircleStatus"
|
||||||
|
ValueChanged="@(value => Update(Value with { CircleStatus = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDropdown Prefix="Tags"
|
||||||
|
Items="tagStatuses"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<TagStatus?>"
|
||||||
|
TValue="TagStatus ?"
|
||||||
|
Value="@Value.TagStatus"
|
||||||
|
ValueChanged="@(value => Update(Value with { TagStatus = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDropdown Prefix="Creators"
|
||||||
|
Items="creatorStatuses"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<CreatorStatus?>"
|
||||||
|
TValue="CreatorStatus ?"
|
||||||
|
Value="@Value.CreatorStatus"
|
||||||
|
ValueChanged="@(value => Update(Value with { CreatorStatus = value }))" />
|
||||||
|
</div>
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<div class="search-filter-control-span-2">
|
||||||
|
<BitDropdown Prefix="Tags"
|
||||||
|
Items="tags"
|
||||||
|
MultiSelect
|
||||||
|
Virtualize
|
||||||
|
ShowSearchBox
|
||||||
|
AutoFocusSearchBox
|
||||||
|
Chips
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<int>"
|
||||||
|
TValue="int"
|
||||||
|
Values="@Value.TagIds"
|
||||||
|
ValuesChanged="@(values => Update(Value with { TagIds = [.. values] }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitCheckbox Label="Include All Tags"
|
||||||
|
Value="@Value.IncludeAllTags"
|
||||||
|
ValueChanged="@(value => Update(Value with { IncludeAllTags = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1"></div>
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<div class="search-filter-control-span-2">
|
||||||
|
<BitDropdown Prefix="Creators"
|
||||||
|
Items="creators"
|
||||||
|
MultiSelect
|
||||||
|
Virtualize
|
||||||
|
ShowSearchBox
|
||||||
|
AutoFocusSearchBox
|
||||||
|
Chips
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<int>"
|
||||||
|
TValue="int"
|
||||||
|
Values="@Value.CreatorIds"
|
||||||
|
ValuesChanged="@(values => Update(Value with { CreatorIds = [.. values] }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitCheckbox Label="Include All Creators"
|
||||||
|
Value="@Value.IncludeAllCreators"
|
||||||
|
ValueChanged="@(value => Update(Value with { IncludeAllCreators = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1"></div>
|
||||||
|
<!-- Row 5 -->
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitCheckbox Label="Show Only Favorite Works"
|
||||||
|
Value="@Value.ShowFavorite"
|
||||||
|
ValueChanged="@(value => Update(Value with { ShowFavorite = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitCheckbox Label="Show Only Invalid Works"
|
||||||
|
Value="@Value.ShowInvalid"
|
||||||
|
ValueChanged="@(value => Update(Value with { ShowInvalid = value }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-2"></div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDropdown Label="Age Ratings"
|
||||||
|
MultiSelect
|
||||||
|
Items="ageRatings"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<AgeRating>"
|
||||||
|
TValue="AgeRating"
|
||||||
|
Values="@Value.AgeRatings"
|
||||||
|
ValuesChanged="@(values => Update(Value with { AgeRatings = [.. values] }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDatePicker Label="Release Date Start"
|
||||||
|
ShowClearButton="true"
|
||||||
|
Value="@ToDto(Value.ReleaseDateStart)"
|
||||||
|
ValueChanged="@(value => Update(Value with { ReleaseDateStart = FromDto(value) }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-1">
|
||||||
|
<BitDatePicker Label="Release Date End"
|
||||||
|
ShowClearButton="true"
|
||||||
|
Value="@ToDto(Value.ReleaseDateEnd)"
|
||||||
|
ValueChanged="@(value => Update(Value with { ReleaseDateEnd = FromDto(value) }))" />
|
||||||
|
</div>
|
||||||
|
<div class="search-filter-control-span-2">
|
||||||
|
<BitDropdown Prefix="Sort"
|
||||||
|
Items="sortOptions"
|
||||||
|
Placeholder="Select..."
|
||||||
|
TItem="BitDropdownItem<VoiceWorkSort>"
|
||||||
|
TValue="VoiceWorkSort"
|
||||||
|
Value="@Value.Sort"
|
||||||
|
ValueChanged="@(value => Update(Value with { Sort = value }))" />
|
||||||
|
</div>
|
||||||
|
@* <div class="search-filter-control-span-1">
|
||||||
|
<BitSlider Label="Downloads" Min="0" Max="100000" Value="MinDownloads" ValueChanged="OnMinDownloadsChanged" />
|
||||||
|
</div> *@
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Inject]
|
||||||
|
ILookupDataService Lookups { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public VoiceWorkFilterState Value { get; set; } = new();
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback<VoiceWorkFilterState> ValueChanged { get; set; }
|
||||||
|
|
||||||
|
List<BitDropdownItem<Locale>> locales = [];
|
||||||
|
List<BitDropdownItem<SaleStatus?>> saleStatuses = [];
|
||||||
|
List<BitDropdownItem<CircleStatus?>> circleStatuses = [];
|
||||||
|
List<BitDropdownItem<TagStatus?>> tagStatuses = [];
|
||||||
|
List<BitDropdownItem<CreatorStatus?>> creatorStatuses = [];
|
||||||
|
List<BitDropdownItem<Language>> languages = [];
|
||||||
|
List<BitDropdownItem<AgeRating>> ageRatings = [];
|
||||||
|
List<BitDropdownItem<VoiceWorkSort>> sortOptions = [];
|
||||||
|
List<BitDropdownItem<int>> tags = [];
|
||||||
|
List<BitDropdownItem<int>> 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);
|
||||||
|
}
|
||||||
9
JSMR.UI.Blazor/Enums/VoiceWorkSort.cs
Normal file
9
JSMR.UI.Blazor/Enums/VoiceWorkSort.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace JSMR.UI.Blazor.Enums;
|
||||||
|
|
||||||
|
public enum VoiceWorkSort
|
||||||
|
{
|
||||||
|
ReleaseDateNewToOld,
|
||||||
|
ReleaseDateOldToNew,
|
||||||
|
BestSelling,
|
||||||
|
MostFavorited
|
||||||
|
}
|
||||||
233
JSMR.UI.Blazor/Filters/VoiceWorkFilterState.cs
Normal file
233
JSMR.UI.Blazor/Filters/VoiceWorkFilterState.cs
Normal file
@@ -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<int>();
|
||||||
|
public bool IncludeAllTags { get; init; }
|
||||||
|
public int[] CreatorIds { get; init; } = Array.Empty<int>();
|
||||||
|
public bool IncludeAllCreators { get; init; }
|
||||||
|
public Language[] SupportedLanguages { get; init; } = Array.Empty<Language>();
|
||||||
|
public AgeRating[] AgeRatings { get; init; } = Array.Empty<AgeRating>();
|
||||||
|
|
||||||
|
// 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<string, string?> ToQuery()
|
||||||
|
{
|
||||||
|
Dictionary<string, string?> query = [];
|
||||||
|
|
||||||
|
void Set(string key, string? value, bool includeWhenEmpty = false)
|
||||||
|
{
|
||||||
|
if (includeWhenEmpty || !string.IsNullOrWhiteSpace(value))
|
||||||
|
query[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Join<T>(IEnumerable<T> 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<T>(string key) where T : struct, Enum
|
||||||
|
=> dict.TryGetValue(key, out var v) && Enum.TryParse<T>(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<int>();
|
||||||
|
TEnum[] GetEnums<TEnum>(string key) where TEnum : struct, Enum
|
||||||
|
=> Get(key)?.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => Enum.TryParse<TEnum>(s, true, out var e) ? e : (TEnum?)null).Where(e => e is not null).Select(e => e!.Value).ToArray()
|
||||||
|
?? Array.Empty<TEnum>();
|
||||||
|
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") ?? Locale.English,
|
||||||
|
|
||||||
|
SaleStatus = GetEnum<SaleStatus>("sale"),
|
||||||
|
CircleStatus = GetEnum<CircleStatus>("circle"),
|
||||||
|
TagStatus = GetEnum<TagStatus>("tag"),
|
||||||
|
CreatorStatus = GetEnum<CreatorStatus>("creator"),
|
||||||
|
|
||||||
|
TagIds = GetInts("tagIds"),
|
||||||
|
IncludeAllTags = Has("allTags"),
|
||||||
|
CreatorIds = GetInts("creatorIds"),
|
||||||
|
IncludeAllCreators = Has("allCreators"),
|
||||||
|
SupportedLanguages = GetEnums<Language>("langs"),
|
||||||
|
AgeRatings = GetEnums<AgeRating>("ages"),
|
||||||
|
|
||||||
|
ShowFavorite = Has("fav"),
|
||||||
|
ShowInvalid = Has("invalid"),
|
||||||
|
|
||||||
|
ReleaseDateStart = GetDate("rs"),
|
||||||
|
ReleaseDateEnd = GetDate("re"),
|
||||||
|
|
||||||
|
Sort = GetEnum<VoiceWorkSort>("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<VoiceWorkSortField>[] 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,452 +1,114 @@
|
|||||||
@page "/voiceworks"
|
@page "/voiceworks"
|
||||||
@using JSMR.Application.Common.Search
|
@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.Application.VoiceWorks.Queries.Search
|
||||||
@using JSMR.Domain.Enums
|
|
||||||
@using JSMR.Domain.ValueObjects
|
|
||||||
@using JSMR.UI.Blazor.Components
|
@using JSMR.UI.Blazor.Components
|
||||||
|
@using JSMR.UI.Blazor.Filters
|
||||||
@using JSMR.UI.Blazor.Services
|
@using JSMR.UI.Blazor.Services
|
||||||
|
@using Microsoft.AspNetCore.WebUtilities
|
||||||
@inject VoiceWorksClient Client
|
@inject VoiceWorksClient Client
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@implements IAsyncDisposable;
|
||||||
|
|
||||||
<PageTitle>Voice Works</PageTitle>
|
<PageTitle>Voice Works</PageTitle>
|
||||||
|
|
||||||
<h3>Voice Works</h3>
|
<h3>Voice Works</h3>
|
||||||
|
|
||||||
<div class="search-filter-control-container">
|
<VoiceWorkFilters Value="@FilterState" ValueChanged="OnFilterStateChanged" />
|
||||||
@* <div class="search-filter-control-span-4">
|
|
||||||
<JTextBox Label="Keywords" Value="@Keywords" ValueChanged="OnKeywordsChanged" Immediate="true" DebounceInterval="1500" />
|
|
||||||
</div> *@
|
|
||||||
<div class="search-filter-control-span-2">
|
|
||||||
<BitTextField Prefix="Keywords" Value="@Keywords" ValueChanged="OnKeywordsChanged" Immediate="true" DebounceTime="500" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDropdown Prefix="Languages"
|
|
||||||
MultiSelect
|
|
||||||
Items="Languages"
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<Language>"
|
|
||||||
TValue="Language"
|
|
||||||
Values="SupportedLanguages"
|
|
||||||
ValuesChanged="OnSupportedLanguagesChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDropdown Prefix="Locale"
|
|
||||||
Items="Locales"
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<Locale>"
|
|
||||||
TValue="Locale"
|
|
||||||
Value="Locale"
|
|
||||||
ValueChanged="OnLocaleChanged" />
|
|
||||||
</div>
|
|
||||||
<!-- Row 2 -->
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDropdown Prefix="Sale Status"
|
|
||||||
Items="SaleStatuses"
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<SaleStatus?>"
|
|
||||||
TValue="SaleStatus?"
|
|
||||||
Value="SelectedSaleStatus"
|
|
||||||
ValueChanged="OnSaleStatusChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDropdown Prefix="Circles"
|
|
||||||
Items="CircleStatuses"
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<CircleStatus?>"
|
|
||||||
TValue="CircleStatus?"
|
|
||||||
Value="SelectedCircleStatus"
|
|
||||||
ValueChanged="OnCircleStatusChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDropdown Prefix="Tags"
|
|
||||||
Items="TagStatuses"
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<TagStatus?>"
|
|
||||||
TValue="TagStatus?"
|
|
||||||
Value="SelectedTagStatus"
|
|
||||||
ValueChanged="OnTagStatusChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDropdown Prefix="Creators"
|
|
||||||
Items="CreatorStatuses"
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<CreatorStatus?>"
|
|
||||||
TValue="CreatorStatus?"
|
|
||||||
Value="SelectedCreatorStatus"
|
|
||||||
ValueChanged="OnCreatorStatusChanged" />
|
|
||||||
</div>
|
|
||||||
<!-- Row 3 -->
|
|
||||||
<div class="search-filter-control-span-2">
|
|
||||||
<BitDropdown Prefix="Tags"
|
|
||||||
Items="Tags"
|
|
||||||
MultiSelect
|
|
||||||
Virtualize
|
|
||||||
ShowSearchBox
|
|
||||||
AutoFocusSearchBox
|
|
||||||
Chips
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<int>"
|
|
||||||
TValue="int"
|
|
||||||
Values="SelectedTagIds"
|
|
||||||
ValuesChanged="OnTagIdsChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitCheckbox Label="Include All Tags" Value="IncludeAllTags" ValueChanged="OnIncludeAllTagsChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1"></div>
|
|
||||||
<!-- Row 4 -->
|
|
||||||
<div class="search-filter-control-span-2">
|
|
||||||
<BitDropdown Prefix="Creators"
|
|
||||||
Items="Creators"
|
|
||||||
MultiSelect
|
|
||||||
Virtualize
|
|
||||||
ShowSearchBox
|
|
||||||
AutoFocusSearchBox
|
|
||||||
Chips
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<int>"
|
|
||||||
TValue="int"
|
|
||||||
Values="SelectedCreatorIds"
|
|
||||||
ValuesChanged="OnCreatorIdsChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitCheckbox Label="Include All Creators" Value="IncludeAllCreators" ValueChanged="OnIncludeAllCreatorsChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1"></div>
|
|
||||||
<!-- Row 5 -->
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitCheckbox Label="Show Only Favorite Works" Value="ShowOnlyFavoriteWorks" ValueChanged="OnShowOnlyFavoriteWorksChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitCheckbox Label="Show Only Invalid Works" Value="ShowOnlyInvalidWorks" ValueChanged="OnShowOnlyInvalidWorksChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-2"></div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDropdown Label="Age Ratings"
|
|
||||||
MultiSelect
|
|
||||||
Items="AgeRatings"
|
|
||||||
Placeholder="Select..."
|
|
||||||
TItem="BitDropdownItem<AgeRating>"
|
|
||||||
TValue="AgeRating"
|
|
||||||
Values="SelectedAgeRatings"
|
|
||||||
ValuesChanged="OnAgeRatingsChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDatePicker Label="Release Date Start" ShowClearButton="true" Value="ReleaseDateStart" ValueChanged="OnReleaseDateStartChanged" />
|
|
||||||
</div>
|
|
||||||
<div class="search-filter-control-span-1">
|
|
||||||
<BitDatePicker Label="Release Date End" ShowClearButton="true" Value="ReleaseDateEnd" ValueChanged="OnReleaseDateEndChanged" />
|
|
||||||
</div>
|
|
||||||
@* <div class="search-filter-control-span-1">
|
|
||||||
<BitSlider Label="Downloads" Min="0" Max="100000" Value="MinDownloads" ValueChanged="OnMinDownloadsChanged" />
|
|
||||||
</div> *@
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<JProductCollection Products="searchResults?.Items"></JProductCollection>
|
<JProductCollection Products="searchResults?.Items"></JProductCollection>
|
||||||
|
|
||||||
@if (searchResults is not null)
|
@if (searchResults is not null)
|
||||||
{
|
{
|
||||||
<JPagination PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" @bind-TotalItems="searchResults.TotalItems" />
|
<JPagination PageNumber="@FilterState.PageNumber"
|
||||||
|
PageNumberChanged="@(pageNumber => OnFilterStateChanged(FilterState with { PageNumber = pageNumber }))"
|
||||||
|
PageSize="@FilterState.PageSize"
|
||||||
|
PageSizeChanged="@(pageSize => OnFilterStateChanged(FilterState with { PageSize = pageSize, PageNumber = 1 }))"
|
||||||
|
@bind-TotalItems="searchResults.TotalItems" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
public string? Keywords { get; set; }
|
private bool _isAlive = true;
|
||||||
public Locale Locale { get; set; } = Locale.English;
|
private CancellationTokenSource _cts = new();
|
||||||
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<Language> SupportedLanguages { get; set; } = [];
|
|
||||||
public IEnumerable<AgeRating> 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<BitDropdownItem<Locale>> Locales =
|
|
||||||
[
|
|
||||||
new() { Text = "Japanese", Value = Locale.Japanese },
|
|
||||||
new() { Text = "English", Value = Locale.English }
|
|
||||||
];
|
|
||||||
|
|
||||||
List<BitDropdownItem<SaleStatus?>> SaleStatuses =
|
|
||||||
[
|
|
||||||
new() { Text = "Available", Value = SaleStatus.Available },
|
|
||||||
new() { Text = "Upcoming", Value = SaleStatus.Upcoming },
|
|
||||||
new() { Text = "All", Value = null }
|
|
||||||
];
|
|
||||||
|
|
||||||
List<BitDropdownItem<CircleStatus?>> 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<BitDropdownItem<TagStatus?>> 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<BitDropdownItem<CreatorStatus?>> 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<BitDropdownItem<Language>> 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<BitDropdownItem<AgeRating>> AgeRatings =
|
|
||||||
[
|
|
||||||
new() { Text = "All Ages", Value = AgeRating.AllAges },
|
|
||||||
new() { Text = "R15", Value = AgeRating.R15 },
|
|
||||||
new() { Text = "R18", Value = AgeRating.R18 }
|
|
||||||
];
|
|
||||||
|
|
||||||
List<BitDropdownItem<int>> Tags = [];
|
|
||||||
List<BitDropdownItem<int>> Creators = [];
|
|
||||||
|
|
||||||
|
VoiceWorkFilterState FilterState = new();
|
||||||
SearchResult<VoiceWorkSearchResult>? searchResults;
|
SearchResult<VoiceWorkSearchResult>? searchResults;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await UpdateDataAsync(true);
|
NavigationManager.LocationChanged += OnLocationChanged;
|
||||||
await GetTags();
|
|
||||||
await GetCreators();
|
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(
|
if (!_isAlive)
|
||||||
Options: new()
|
|
||||||
{
|
|
||||||
PageNumber = 1,
|
|
||||||
PageSize = 99999
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
SearchTagsResponse? response = await Client.SearchAsync(request);
|
|
||||||
|
|
||||||
if (response is null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Tags = response.Results.Items
|
if (!IsThisPage(e.Location))
|
||||||
.OrderByDescending(x => x.Favorite)
|
|
||||||
.ThenBy(x => x.EnglishName ?? x.Name)
|
|
||||||
.Select(x => new BitDropdownItem<int>() { 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;
|
return;
|
||||||
|
|
||||||
Creators = response.Results.Items.Select(x => new BitDropdownItem<int>() { 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");
|
await JS.InvokeVoidAsync("pageHelpers.scrollToTop");
|
||||||
|
|
||||||
if (resetPageNumber)
|
try
|
||||||
PageNumber = 1;
|
|
||||||
|
|
||||||
SearchVoiceWorksRequest request = new(
|
|
||||||
Options: new()
|
|
||||||
{
|
{
|
||||||
Criteria = new()
|
_cts.Cancel();
|
||||||
{
|
_cts = 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);
|
|
||||||
|
|
||||||
|
SearchVoiceWorksResponse? response = await Client.SearchAsync(FilterState.ToSearchRequest(), _cts.Token);
|
||||||
searchResults = response?.Results;
|
searchResults = response?.Results;
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
public async Task OnKeywordsChanged(string? newKeywords)
|
|
||||||
{
|
{
|
||||||
Keywords = newKeywords;
|
|
||||||
await UpdateDataAsync(true);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnLocaleChanged(Locale locale)
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
Locale = locale;
|
_isAlive = false;
|
||||||
await UpdateDataAsync(true);
|
NavigationManager.LocationChanged -= OnLocationChanged;
|
||||||
}
|
_cts.Cancel();
|
||||||
|
_cts.Dispose();
|
||||||
public async Task OnSaleStatusChanged(SaleStatus? saleStatus)
|
await Task.CompletedTask;
|
||||||
{
|
|
||||||
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<int> tagIds)
|
|
||||||
{
|
|
||||||
SelectedTagIds = [..tagIds];
|
|
||||||
await UpdateDataAsync(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnIncludeAllTagsChanged(bool includeAllTags)
|
|
||||||
{
|
|
||||||
IncludeAllTags = includeAllTags;
|
|
||||||
await UpdateDataAsync(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnCreatorIdsChanged(IEnumerable<int> creatorIds)
|
|
||||||
{
|
|
||||||
SelectedCreatorIds = [..creatorIds];
|
|
||||||
await UpdateDataAsync(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnIncludeAllCreatorsChanged(bool includeAllCreators)
|
|
||||||
{
|
|
||||||
IncludeAllCreators = includeAllCreators;
|
|
||||||
await UpdateDataAsync(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnSupportedLanguagesChanged(IEnumerable<Language> languages)
|
|
||||||
{
|
|
||||||
SupportedLanguages = languages;
|
|
||||||
await UpdateDataAsync(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnAgeRatingsChanged(IEnumerable<AgeRating> 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)
|
|
||||||
{
|
|
||||||
case SaleStatus.Available:
|
|
||||||
return VoiceWorkSortField.ReleaseDate;
|
|
||||||
case SaleStatus.Upcoming:
|
|
||||||
return VoiceWorkSortField.ExpectedReleaseDate;
|
|
||||||
default:
|
|
||||||
return VoiceWorkSortField.AnyReleaseDate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,5 +20,6 @@ builder.Services.AddRadzenComponents();
|
|||||||
builder.Services.AddBitBlazorUIServices();
|
builder.Services.AddBitBlazorUIServices();
|
||||||
|
|
||||||
builder.Services.AddScoped<VoiceWorksClient>();
|
builder.Services.AddScoped<VoiceWorksClient>();
|
||||||
|
builder.Services.AddScoped<ILookupDataService, LookupDataService>();
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
||||||
22
JSMR.UI.Blazor/Services/ILookupDataService.cs
Normal file
22
JSMR.UI.Blazor/Services/ILookupDataService.cs
Normal file
@@ -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<BitDropdownItem<Locale>> GetLocales();
|
||||||
|
List<BitDropdownItem<SaleStatus?>> GetSaleStatuses();
|
||||||
|
List<BitDropdownItem<CircleStatus?>> GetCircleStatuses();
|
||||||
|
List<BitDropdownItem<TagStatus?>> GetTagStatuses();
|
||||||
|
List<BitDropdownItem<CreatorStatus?>> GetCreatorStatuses();
|
||||||
|
List<BitDropdownItem<Language>> GetLanguages();
|
||||||
|
List<BitDropdownItem<AgeRating>> GetAgeRatings();
|
||||||
|
List<BitDropdownItem<VoiceWorkSort>> GetSortOptions();
|
||||||
|
|
||||||
|
Task<List<BitDropdownItem<int>>> GetTagsAsync(CancellationToken cancellationToken = default);
|
||||||
|
Task<List<BitDropdownItem<int>>> GetCreatorsAsync(CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
134
JSMR.UI.Blazor/Services/LookupDataService.cs
Normal file
134
JSMR.UI.Blazor/Services/LookupDataService.cs
Normal file
@@ -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<BitDropdownItem<int>>? _tags;
|
||||||
|
private List<BitDropdownItem<int>>? _creators;
|
||||||
|
|
||||||
|
public List<BitDropdownItem<Locale>> GetLocales() =>
|
||||||
|
[
|
||||||
|
new() { Text = "Japanese", Value = Locale.Japanese },
|
||||||
|
new() { Text = "English", Value = Locale.English }
|
||||||
|
];
|
||||||
|
|
||||||
|
public List<BitDropdownItem<SaleStatus?>> GetSaleStatuses() =>
|
||||||
|
[
|
||||||
|
new() { Text = "Available", Value = SaleStatus.Available },
|
||||||
|
new() { Text = "Upcoming", Value = SaleStatus.Upcoming },
|
||||||
|
new() { Text = "All", Value = null }
|
||||||
|
];
|
||||||
|
|
||||||
|
public List<BitDropdownItem<CircleStatus?>> 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<BitDropdownItem<TagStatus?>> 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<BitDropdownItem<CreatorStatus?>> 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<BitDropdownItem<Language>> 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<BitDropdownItem<AgeRating>> GetAgeRatings() =>
|
||||||
|
[
|
||||||
|
new() { Text = "All Ages", Value = AgeRating.AllAges },
|
||||||
|
new() { Text = "R15", Value = AgeRating.R15 },
|
||||||
|
new() { Text = "R18", Value = AgeRating.R18 }
|
||||||
|
];
|
||||||
|
|
||||||
|
public List<BitDropdownItem<VoiceWorkSort>> 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<List<BitDropdownItem<int>>> 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<TagSearchItem>())
|
||||||
|
.OrderByDescending(x => x.Favorite)
|
||||||
|
.ThenBy(x => x.EnglishName ?? x.Name, StringComparer.Ordinal)
|
||||||
|
.Select(x => new BitDropdownItem<int> { Text = x.EnglishName ?? x.Name, Value = x.TagId })
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return _tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<BitDropdownItem<int>>> 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<CreatorSearchItem>())
|
||||||
|
.OrderBy(x => x.Name, StringComparer.Ordinal)
|
||||||
|
.Select(x => new BitDropdownItem<int> { Text = x.Name, Value = x.CreatorId })
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return _creators;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user