Simplified and enhnaced voice work page.

This commit is contained in:
2025-11-25 12:27:19 -05:00
parent 8eb5a22031
commit f39a7697d7
7 changed files with 680 additions and 411 deletions

View 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;
}
}
}