Added more UI styling. Updated voice work search provider to send back English tag names, if applicable.
All checks were successful
ci / build-test (push) Successful in 2m15s
ci / publish-image (push) Has been skipped

This commit is contained in:
2025-11-15 22:37:15 -05:00
parent 634050c06f
commit 75900a90ef
10 changed files with 245 additions and 12 deletions

View File

@@ -388,16 +388,18 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea
var tagRows = await (
from voiceWorkTag in context.VoiceWorkTags.AsNoTracking()
join tag in context.Tags.AsNoTracking() on voiceWorkTag.TagId equals tag.TagId
join englishTag in context.EnglishTags.AsNoTracking() on tag.TagId equals englishTag.TagId into et
from englishTag in et.DefaultIfEmpty()
where voiceWorkIds.Contains(voiceWorkTag.VoiceWorkId)
orderby voiceWorkTag.VoiceWorkId, voiceWorkTag.Position
select new { voiceWorkTag.VoiceWorkId, voiceWorkTag.TagId, tag.Name }
select new { voiceWorkTag.VoiceWorkId, voiceWorkTag.TagId, tag.Name, EnglishName = englishTag.Name }
).ToListAsync(cancellationToken);
return tagRows
.GroupBy(r => r.VoiceWorkId)
.ToDictionary(
g => g.Key,
g => g.Select(r => new VoiceWorkTagItem { TagId = r.TagId, Name = r.Name }).ToArray()
g => g.Select(r => new VoiceWorkTagItem { TagId = r.TagId, Name = r.EnglishName ?? r.Name }).ToArray()
);
}

View File

@@ -1,5 +1,5 @@
<div class="@ContainerClassees">
<div class="j-image-overlay"></div>
<div class="@OverlayClasses"></div>
<img class="@ImageClasses" loading="@LoadingAttribute" src="@Source" @onload="OnImageLoaded">
</div>
@@ -16,6 +16,9 @@
[Parameter]
public string? ContainerClass { get; set; }
[Parameter]
public string? OverlayClass { get; set; }
[Parameter]
public string? ImageClass { get; set; }
@@ -23,6 +26,7 @@
private string? _lastSource;
private string ContainerClassees => GetContainerClasses();
private string OverlayClasses => GetOverlayClasses();
private string ImageClasses => GetImageClasses();
private string? LoadingAttribute => LazyLoading ? "lazy" : null;
@@ -54,6 +58,24 @@
return string.Join(" ", classNames);
}
private string GetOverlayClasses()
{
List<string> classNames = ["j-image-overlay"];
if (!string.IsNullOrEmpty(OverlayClass))
{
List<string> customClassNames = OverlayClass
.Split(" ")
.Select(className => className.Trim())
.Where(className => !string.IsNullOrWhiteSpace(className))
.ToList();
classNames.AddRange(customClassNames);
}
return string.Join(" ", classNames);
}
private string GetImageClasses()
{
List<string> classNames = ["j-image"];

View File

@@ -1,12 +1,30 @@
@using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Services
@using System.Globalization
<div class="j-card j-voice-work-card">
<div class="j-voice-work-image-container">
<JImage ImageClass="j-voice-work-image" Source="@ImageUrlProvider.GetImageURL(Product.ProductId, Product.HasImage, Product.SalesDate, "main")"></JImage>
<JImage OverlayClass="j-voice-work-image-overlay" ImageClass="j-voice-work-image" Source="@ImageUrlProvider.GetImageUrl(Product, "main")"></JImage>
</div>
<div class="j-voice-work-content">
<div class="j-product-title">@Product.ProductName</div>
<div class="j-product-contributors">
<span class="j-circle">
<MudChip T="string"
Href="https://github.com/MudBlazor/MudBlazor"
Target="_blank"
Variant="Variant.Filled"
Icon="@Icons.Material.Outlined.Circle">@Product.Maker</MudChip>
@foreach (var creator in Product.Creators)
{
<MudChip T="string"
Href="https://github.com/MudBlazor/MudBlazor"
Target="_blank"
Variant="Variant.Filled"
Icon="@Icons.Material.Filled.Person">@creator.Name</MudChip>
}
</span>
</div>
<div class="j-product-description">@Product.Description</div>
<div class="j-tags">
@foreach (var tag in Product.Tags)
@@ -16,10 +34,52 @@
</div>
</div>
<div class="j-voice-work-info">
<div class="j-release-date-container">
<span class="j-icon j-icon-calendar"></span>
<span>@GetSomething(Product)</span>
</div>
<div class="j-wishlist-container">
<span class="j-icon j-icon-star j-icon-color-yellow"></span>
<span>@((Product.WishlistCount ?? 0).ToString("n0"))</span>
</div>
@if (Product.SalesDate is not null)
{
<div class="j-downloads-container">
<span class="j-icon j-icon-bag-fill j-icon-color-green"></span>
<span>@((Product.Downloads ?? 0).ToString("n0"))</span>
</div>
}
</div>
</div>
@code {
[Parameter]
public required VoiceWorkSearchResult Product { get; set; }
private string GetSomething(VoiceWorkSearchResult voiceWork)
{
if (voiceWork.SalesDate.HasValue)
{
return voiceWork.SalesDate.Value.ToString("MMMM d, yyyy", CultureInfo.CurrentCulture);
}
if (voiceWork.PlannedReleaseDate.HasValue)
{
return voiceWork.PlannedReleaseDate.Value.ToString("MMMM d, yyyy", CultureInfo.CurrentCulture);
}
if (voiceWork.ExpectedDate.HasValue)
{
string part = voiceWork.ExpectedDate.Value.Day switch
{
21 => "Late",
11 => "Middle",
_ => "Early"
};
return $"{part} {voiceWork.ExpectedDate.Value.ToString("MMMM yyyy")}";
}
return "Unknown";
}
}

View File

@@ -1,4 +1,5 @@
@page "/voiceworks"
@using JSMR.Application.Common.Search
@using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Components
@using JSMR.UI.Blazor.Services
@@ -6,21 +7,61 @@
<PageTitle>Voice Works</PageTitle>
<h3>VoiceWorks</h3>
<h3>Voice Works</h3>
<JProductCollection Products="items"></JProductCollection>
<JProductCollection Products="searchResults?.Items"></JProductCollection>
@if (searchResults is not null)
{
<JPagination PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" @bind-TotalItems="searchResults.TotalItems" />
}
@code {
VoiceWorkSearchResult[]? items;
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 100;
SearchResult<VoiceWorkSearchResult>? searchResults;
protected override async Task OnInitializedAsync()
{
await UpdateDataAsync(true);
}
private async Task UpdateDataAsync(bool resetPageNumber)
{
if (resetPageNumber)
PageNumber = 1;
SearchVoiceWorksRequest request = new(
Options: new()
{
Criteria = new()
{
SupportedLanguages = [Domain.Enums.Language.English]
},
SortOptions =
[
new(VoiceWorkSortField.ReleaseDate, Application.Common.Search.SortDirection.Descending)
],
PageNumber = PageNumber,
PageSize = PageSize
}
);
var result = await Client.SearchAsync(request);
SearchVoiceWorksResponse? response = await Client.SearchAsync(request);
items = result?.Results.Items ?? [];
searchResults = response?.Results;
}
public async Task OnPageNumberChanged(int newPageNumber)
{
PageNumber = newPageNumber;
await UpdateDataAsync(false);
}
public async Task OnPageSizeChanged(int newPageSize)
{
PageSize = newPageSize;
await UpdateDataAsync(true);
}
}

View File

@@ -1,7 +1,14 @@
namespace JSMR.UI.Blazor.Services;
using JSMR.Application.VoiceWorks.Queries.Search;
namespace JSMR.UI.Blazor.Services;
public static class ImageUrlProvider
{
public static string GetImageUrl(VoiceWorkSearchResult voiceWork, string size)
{
return GetImageURL(voiceWork.OriginalProductId ?? voiceWork.ProductId, voiceWork.HasImage, voiceWork.SalesDate, size);
}
public static string GetImageURL(string? productId, bool hasImage, DateTime? salesDate, string size)
{
string folder = "modpub";

View File

@@ -355,6 +355,10 @@ code {
flex-shrink: 0;
}
.j-voice-work-image-overlay {
border-radius: 20px;
}
.j-voice-work-image {
border-radius: 20px;
}
@@ -377,6 +381,11 @@ code {
text-shadow: 1px 1px 2px black;
}
.j-product-contributors {
font-size: 1rem;
font-family: "Poppins", "M+ 1p";
}
.j-product-description {
/* color: #7C8099; */
font-size: 1rem;
@@ -389,15 +398,96 @@ code {
.j-voice-work-info {
width: 240px;
align-items: flex-end;
display: flex;
flex-direction: column;
padding: .5rem;
/*background: black;*/
border-top-right-radius: 20px;
border-bottom-right-radius: 20px;
gap: .5rem;
font-size: 1rem;
text-shadow: 1px 1px 2px rgb(16, 36, 50);
}
.j-voice-work-card > .j-voice-work-info {
flex-shrink: 0;
}
.j-release-date-container {
display: flex;
align-items: center;
gap: .5rem;
font-size: 1rem;
font-weight: 500;
}
.j-wishlist-container {
display: flex;
align-items: center;
gap: .5rem;
font-size: 1rem;
font-weight: 500;
color: #ffe073;
}
.j-downloads-container {
display: flex;
align-items: center;
gap: .5rem;
font-size: 1rem;
font-weight: 500;
}
/* Tags */
.j-tags {
display: flex;
gap: 1rem;
gap: .75rem .5rem;
flex-wrap: wrap;
/*gap: .5rem 1rem;*/
}
.j-tag {
background-color: var(--tag-background-color);
color: var(--tag-text-color);
border: var(--tag-border);
border-radius: var(--tag-border-radius);
padding: var(--tag-padding);
/*padding: 0;
background-color: transparent;
text-shadow: 1px 1px 2px black;
font-weight: 500;*/
}
/* Icons */
.j-icon {
mask-size: auto;
align-self: center;
background: rgb(180,200, 214);
height: 16px;
width: 16px;
}
.j-icon-color-yellow {
background: #ffe073;
}
.j-icon-color-green {
background: #388E3C;
}
.j-icon-calendar {
mask-image: url("../svg/calendar.svg");
}
.j-icon-star {
mask-image: url("../svg/star-fill.svg");
}
.j-icon-bag {
mask-image: url("../svg/bag.svg");
}
.j-icon-bag-fill {
mask-image: url("../svg/bag-fill.svg");
}

View File

@@ -13,7 +13,8 @@
--tag-text-color: rgb(210,220,230);
--tag-border: none;
--tag-border-radius: 0.8em;
--tag-padding: 0.3em 1em;
--tag-padding-old: 0.3em 1em;
--tag-padding: 0.5em 1em;
--tag-margin: 1em 0.5em 0 0;
--tag-hover-background-color: #596f7e;
--tag-hover-text-color: rgb(240,250,254);

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bag-fill" viewBox="0 0 16 16">
<path d="M8 1a2.5 2.5 0 0 1 2.5 2.5V4h-5v-.5A2.5 2.5 0 0 1 8 1m3.5 3v-.5a3.5 3.5 0 1 0-7 0V4H1v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4z"/>
</svg>

After

Width:  |  Height:  |  Size: 269 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bag" viewBox="0 0 16 16">
<path d="M8 1a2.5 2.5 0 0 1 2.5 2.5V4h-5v-.5A2.5 2.5 0 0 1 8 1m3.5 3v-.5a3.5 3.5 0 1 0-7 0V4H1v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4zM2 5h12v9a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z"/>
</svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar3" viewBox="0 0 16 16">
<path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2M1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857z"/>
<path d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2m3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
</svg>

After

Width:  |  Height:  |  Size: 664 B