Updated UI. Fixed circle search performance.
All checks were successful
ci / build-test (push) Successful in 1m43s
ci / publish-image (push) Has been skipped

This commit is contained in:
2025-11-10 18:55:46 -05:00
parent 840bec72d2
commit 9cd9230cec
43 changed files with 1105 additions and 164 deletions

View File

@@ -1,57 +1,265 @@
@page "/circles"
@using JSMR.Application.Circles.Queries.Search
@using JSMR.Application.Common.Search
@using JSMR.UI.Blazor.Components
@using JSMR.UI.Blazor.Services
@inject VoiceWorksClient Client
@inject IJSRuntime JS
@inject HttpClient Http
<PageTitle>Circles</PageTitle>
<h1>Circles</h1>
<p>This component demonstrates fetching data from the server.</p>
<div class="search-filter-control-container">
<div class="search-filter-control-span-3">
<MudTextField @bind-Value="Keywords" Placeholder="Circle Name or Maker Id" Immediate="true" DebounceInterval="500" Variant="Variant.Outlined" Margin="Margin.Dense" Adornment="@Adornment.End" AdornmentIcon="@Icons.Material.Outlined.Search" />
</div>
<div class="search-filter-control-span-1">
<MudSelect @bind-Value="SelectedCircleStatus" Placeholder="Circle Status" Dense="true" Variant="Variant.Outlined" Margin="Margin.Dense">
<MudSelectItem Value="@CircleStatus.NotBlacklisted.ToString()">Not Blacklisted</MudSelectItem>
<MudSelectItem Value="@CircleStatus.Favorited.ToString()">Favorite</MudSelectItem>
<MudSelectItem Value="@CircleStatus.Blacklisted.ToString()">Blacklisted</MudSelectItem>
<MudSelectItem Value="@CircleStatus.Spam.ToString()">Spam</MudSelectItem>
<MudSelectItem Value="@String.Empty">All</MudSelectItem>
</MudSelect>
</div>
</div>
@* @if (forecasts == null)
@if (searchResults is null)
{
<p><em>Loading...</em></p>
<p>Loading</p>
}
else if (searchResults.Items.Length == 0)
{
<p>No results.</p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th aria-label="Temperature in Celsius">Temp. (C)</th>
<th aria-label="Temperature in Farenheit">Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
} *@
<MudDataGrid Items="@searchResults.Items" Style="table-layout: fixed">
<Columns>
<TemplateColumn HeaderStyle="width: 12em">
<CellTemplate>
<JImage ContainerClass="j-circle-image-container-mini" ImageClass="j-circle-image-mini" Source="@ImageUrlProvider.GetImageURL(context.Item.LatestProductId, context.Item.LatestVoiceWorkHasImage ?? false, context.Item.LatestVoiceWorkSalesDate, "main")" />
</CellTemplate>
</TemplateColumn>
<PropertyColumn Property="x => x.Name" Title="Name" />
<PropertyColumn Property="x => x.MakerId" Title="Maker Id" HeaderStyle="width: 12em" />
<TemplateColumn Title="Status" HeaderStyle="width: 10em">
<CellTemplate>
@if (context.Item.Favorite)
{
<MudChip T="string" Label="true" Color="Color.Info" Style="width: 100%">Favorite</MudChip>
}
else if (context.Item.Blacklisted)
{
<MudChip T="string" Label="true" Color="Color.Warning" Style="color:black; width: 100%">Blacklisted</MudChip>
}
else if (context.Item.Spam)
{
<MudChip T="string" Label="true" Color="Color.Error" Style="width: 100%">Spam</MudChip>
}
else
{
<MudChip T="string" Label="true" Style="width: 100%">Normal</MudChip>
}
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="Rating" HeaderStyle="width: 8em">
<CellTemplate>
@if (context.Item.Releases > 0)
{
<div class="circle-star-container">
<div class="circle-star @GetStarRatingClass(context.Item)"></div>
</div>
}
</CellTemplate>
</TemplateColumn>
<PropertyColumn Property="x => x.Downloads" Title="Downloads" HeaderStyle="width: 12em" />
<PropertyColumn Property="x => x.Releases" Title="Releases" HeaderStyle="width: 12em" />
<PropertyColumn Property="x => x.Pending" Title="Pending" HeaderStyle="width: 12em" />
<TemplateColumn Title="First Release Date" HeaderStyle="width: 14em">
<CellTemplate>
<MudText>@context.Item.FirstReleaseDate?.ToString("MMMM d, yyyy")</MudText>
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="Latest Release Date" HeaderStyle="width: 14em">
<CellTemplate>
<MudText>@context.Item.LatestReleaseDate?.ToString("MMMM d, yyyy")</MudText>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
@* <div class="items-container circle-items-container">
@foreach (var item in searchResults.Items)
{
<MudPaper Outlined="true">
<JImage @key="item.CircleId" ContainerClass="j-circle-image-container" ImageClass="j-circle-image" Source="@ImageUrlProvider.GetImageURL(item.LatestProductId, item.LatestVoiceWorkHasImage ?? false, item.LatestVoiceWorkSalesDate, "main")" />
<MudText Typo="Typo.h6">@item.Name</MudText>
<div>@item.Releases</div>
<div>@item.Pending</div>
@if (item.Releases > 0)
{
<div class="circle-star-container">
<div class="circle-star @GetStarRatingClass(item)"></div>
</div>
}
<div>@item.Downloads</div>
<MudChip T="string" Variant="Variant.Outlined" Icon="@Icons.Material.Filled.Download" Color="Color.Info">@item.Downloads.ToString("n0")</MudChip>
<div>@item.FirstReleaseDate?.ToString("MMMM d, yyyy")</div>
<div>@item.LatestReleaseDate?.ToString("MMMM d, yyyy")</div>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Favorite" Color="@(item.Favorite? Color.Secondary: Color.Dark)" />
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Block" Color="@(item.Blacklisted? Color.Secondary: Color.Dark)" />
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.RestoreFromTrash" Color="@(item.Spam? Color.Secondary: Color.Dark)" />
</MudPaper>
}
</div> *@
<JPagination @bind-PageNumber="PageNumber" @bind-PageSize="pageSize" @bind-TotalItems="searchResults.TotalItems" />
@* <MudPagination ShowFirstButton="true" ShowLastButton="true" Count="@((int)Math.Ceiling((decimal)searchResults.TotalItems / (decimal)100))" @bind-Selected="@PageNumber" Class="j-pager" /> *@
}
<style>
.mud-table-root {
table-layout: fixed;
}
.j-pager {
position: fixed;
bottom: 0;
left: 0;
right: 0;
justify-content: center;
z-index: 2;
background: var(--mud-palette-background);
padding: .5em 1em;
}
</style>
@code {
// private WeatherForecast[]? forecasts;
private string? keywords;
// protected override async Task OnInitializedAsync()
// {
// forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
// }
public string? Keywords
{
get { return keywords; }
set
{
keywords = value;
_ = UpdateDataAsync(true);
}
}
// public class WeatherForecast
// {
// public DateOnly Date { get; set; }
private string circleStatus = string.Empty;
// public int TemperatureC { get; set; }
public string SelectedCircleStatus
{
get { return circleStatus; }
set
{
circleStatus = value;
_ = UpdateDataAsync(true);
}
}
// public string? Summary { get; set; }
private int pageNumber = 1;
// public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
// }
public int PageNumber
{
get { return pageNumber; }
set
{
pageNumber = value;
_ = UpdateDataAsync(false);
}
}
int pageSize = 100;
SearchResult<CircleSearchItem>? searchResults;
protected override Task OnInitializedAsync()
{
_ = LoadCirclesAsync();
return Task.CompletedTask;
}
private async Task LoadCirclesAsync()
{
SearchCirclesRequest request = new(
Options: new()
{
PageNumber = PageNumber,
PageSize = pageSize,
Criteria = new()
{
Name = Keywords,
Status = string.IsNullOrWhiteSpace(SelectedCircleStatus) == false ? Enum.Parse<CircleStatus>(SelectedCircleStatus) : null
},
SortOptions =
[
new(CircleSortField.Name, Application.Common.Search.SortDirection.Ascending)
]
}
);
await JS.InvokeVoidAsync("pageHelpers.scrollToTop");
var result = await Client.SearchAsync(request);
searchResults = result?.Results ?? new();
await InvokeAsync(StateHasChanged);
}
private async Task UpdateDataAsync(bool resetPageNumber)
{
if (resetPageNumber)
pageNumber = 1;
await LoadCirclesAsync();
}
private string GetStarRatingClass(CircleSearchItem item)
{
double averageDownloads = item.Downloads / item.Releases;
if (averageDownloads < 100)
{
return "circle-star-poor";
}
if (averageDownloads < 250)
{
return "circle-star-below-average";
}
if (averageDownloads < 500)
{
return "circle-star-average";
}
if (averageDownloads < 1000)
{
return "circle-star-above-average";
}
if (averageDownloads < 2000)
{
return "circle-star-popular";
}
if (averageDownloads < 4000)
{
return "circle-star-super-popular";
}
if (averageDownloads < 8000)
{
return "circle-star-ultra-popular";
}
return "circle-star-god-tier";
}
}

View File

@@ -1,18 +1,122 @@
@page "/creators"
@inject VoiceWorksClient Client
@inject IJSRuntime JS
@using JSMR.Application.Common.Search
@using JSMR.Application.Creators.Queries.Search
@using JSMR.Application.Creators.Queries.Search.Contracts
@using JSMR.UI.Blazor.Services
<PageTitle>Creators</PageTitle>
<h1>Creators</h1>
<p role="status">Current count: @currentCount</p>
<MudTextField @bind-Value="Keywords" Immediate="true" DebounceInterval="500" Label="Filter" Variant="Variant.Text" Adornment="@Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Search" />
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@if (searchResults is null)
{
<p>Loading…</p>
}
else if (searchResults.Items.Length == 0)
{
<p>No results.</p>
}
else
{
<MudDataGrid Items="@searchResults.Items" Style="table-layout: fixed">
<Columns>
<PropertyColumn Property="x => x.Name" Title="Name" />
<PropertyColumn Property="x => x.VoiceWorkCount" Title="Voice Works" HeaderStyle="width: 14em" />
<TemplateColumn Title="Favorite" HeaderStyle="width: 8em">
<CellTemplate>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Favorite" Color="@(context.Item.Favorite? Color.Secondary: Color.Dark)" />
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="Blacklisted" HeaderStyle="width: 8em">
<CellTemplate>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Block" Color="@(context.Item.Blacklisted? Color.Secondary: Color.Dark)" />
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
<MudPagination ShowFirstButton="true" ShowLastButton="true" Count="@((int)Math.Ceiling((decimal)searchResults.TotalItems / (decimal)100))" @bind-Selected="@PageNumber" Class="j-pager" />
}
<style>
.mud-table-root {
table-layout: fixed;
}
.j-pager {
position: fixed;
bottom: 0;
left: 0;
right: 0;
justify-content: center;
z-index: 2;
background: var(--mud-palette-background);
padding: .5em 1em;
}
</style>
@code {
private int currentCount = 0;
private string? keywords;
private void IncrementCount()
public string? Keywords
{
currentCount++;
get { return keywords; }
set
{
keywords = value;
_ = LoadTagsAsync();
}
}
}
private int pageNumber = 1;
public int PageNumber
{
get { return pageNumber; }
set
{
pageNumber = value;
_ = LoadTagsAsync();
}
}
int pageSize = 100;
SearchResult<CreatorSearchItem>? searchResults;
protected override Task OnInitializedAsync()
{
_ = LoadTagsAsync();
return Task.CompletedTask;
}
private async Task LoadTagsAsync()
{
SearchCreatorsRequest request = new(
Options: new()
{
PageNumber = PageNumber,
PageSize = pageSize,
Criteria = new()
{
Name = Keywords
},
SortOptions =
[
new(CreatorSortField.Name, Application.Common.Search.SortDirection.Ascending)
]
}
);
await JS.InvokeVoidAsync("pageHelpers.scrollToTop");
var result = await Client.SearchAsync(request);
searchResults = result?.Results ?? new();
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,6 +1,7 @@
@page "/"
@inject VoiceWorksClient Client
@using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Services
<PageTitle>Home</PageTitle>

View File

@@ -1,13 +1,17 @@
@page "/tags"
@inject VoiceWorksClient Client
@inject IJSRuntime JS
@using JSMR.Application.Common.Search
@using JSMR.Application.Tags.Queries.Search
@using JSMR.Application.Tags.Queries.Search.Contracts
@using JSMR.UI.Blazor.Services
<PageTitle>Tags</PageTitle>
<h1>Tags</h1>
<MudTextField @bind-Value="Keywords" Immediate="true" DebounceInterval="500" Label="Filter" Variant="Variant.Text" Adornment="@Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Search" />
@if (searchResults is null)
{
<p>Loading…</p>
@@ -18,13 +22,21 @@ else if (searchResults.Items.Length == 0)
}
else
{
<MudDataGrid Items="@searchResults.Items">
<MudDataGrid Items="@searchResults.Items" Style="table-layout: fixed">
<Columns>
<PropertyColumn Property="x => x.Name" Title="Name" />
<PropertyColumn Property="x => x.EnglishName" Title="English Name" />
<PropertyColumn Property="x => x.VoiceWorkCount" Title="Voice Works" />
<PropertyColumn Property="x => x.Favorite" Title="Favorite" />
<PropertyColumn Property="x => x.Blacklisted" Title="Blacklisted" />
<PropertyColumn Property="x => x.VoiceWorkCount" Title="Voice Works" HeaderStyle="width: 14em" />
<TemplateColumn Title="Favorite" HeaderStyle="width: 8em">
<CellTemplate>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Favorite" Color="@(context.Item.Favorite ? Color.Secondary : Color.Dark)" @onclick="@(e => IncrementCount(context.Item))" />
</CellTemplate>
</TemplateColumn>
<TemplateColumn Title="Blacklisted" HeaderStyle="width: 8em">
<CellTemplate>
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Block" Color="@(context.Item.Blacklisted? Color.Secondary: Color.Dark)" />
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
@@ -32,6 +44,10 @@ else
}
<style>
.mud-table-root {
table-layout: fixed;
}
.j-pager {
position: fixed;
bottom: 0;
@@ -45,6 +61,18 @@ else
</style>
@code {
private string? keywords;
public string? Keywords
{
get { return keywords; }
set
{
keywords = value;
_ = LoadTagsAsync();
}
}
private int pageNumber = 1;
public int PageNumber
@@ -76,7 +104,7 @@ else
PageSize = pageSize,
Criteria = new()
{
Name = Keywords
},
SortOptions =
[
@@ -85,6 +113,7 @@ else
}
);
await JS.InvokeVoidAsync("pageHelpers.scrollToTop");
var result = await Client.SearchAsync(request);
searchResults = result?.Results ?? new();
@@ -92,4 +121,8 @@ else
await InvokeAsync(StateHasChanged);
}
private void IncrementCount(TagSearchItem item)
{
//item.Favorite = !item.Favorite;
}
}

View File

@@ -1,5 +1,6 @@
@page "/voiceworks"
@using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Services
@inject VoiceWorksClient Client
<PageTitle>Voice Works</PageTitle>