From 75900a90efd80a18a4af7799d0c86e37c781cdc1 Mon Sep 17 00:00:00 2001 From: Brian Bicknell Date: Sat, 15 Nov 2025 22:37:15 -0500 Subject: [PATCH] Added more UI styling. Updated voice work search provider to send back English tag names, if applicable. --- .../VoiceWorks/VoiceWorkSearchProvider.cs | 6 +- JSMR.UI.Blazor/Components/JImage.razor | 24 ++++- JSMR.UI.Blazor/Components/JProduct.razor | 62 ++++++++++++- JSMR.UI.Blazor/Pages/VoiceWorks.razor | 51 +++++++++- JSMR.UI.Blazor/Services/ImageUrlProvider.cs | 9 +- JSMR.UI.Blazor/wwwroot/css/app.css | 92 ++++++++++++++++++- JSMR.UI.Blazor/wwwroot/css/theme-frozen.css | 3 +- JSMR.UI.Blazor/wwwroot/svg/bag-fill.svg | 3 + JSMR.UI.Blazor/wwwroot/svg/bag.svg | 3 + JSMR.UI.Blazor/wwwroot/svg/calendar.svg | 4 + 10 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 JSMR.UI.Blazor/wwwroot/svg/bag-fill.svg create mode 100644 JSMR.UI.Blazor/wwwroot/svg/bag.svg create mode 100644 JSMR.UI.Blazor/wwwroot/svg/calendar.svg diff --git a/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs b/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs index 9965094..fd5e13b 100644 --- a/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs +++ b/JSMR.Infrastructure/Data/Repositories/VoiceWorks/VoiceWorkSearchProvider.cs @@ -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() ); } diff --git a/JSMR.UI.Blazor/Components/JImage.razor b/JSMR.UI.Blazor/Components/JImage.razor index c5d0eb0..8653961 100644 --- a/JSMR.UI.Blazor/Components/JImage.razor +++ b/JSMR.UI.Blazor/Components/JImage.razor @@ -1,5 +1,5 @@ 
-
+
@@ -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 classNames = ["j-image-overlay"]; + + if (!string.IsNullOrEmpty(OverlayClass)) + { + List customClassNames = OverlayClass + .Split(" ") + .Select(className => className.Trim()) + .Where(className => !string.IsNullOrWhiteSpace(className)) + .ToList(); + + classNames.AddRange(customClassNames); + } + + return string.Join(" ", classNames); + } + private string GetImageClasses() { List classNames = ["j-image"]; diff --git a/JSMR.UI.Blazor/Components/JProduct.razor b/JSMR.UI.Blazor/Components/JProduct.razor index 714f934..cbb9126 100644 --- a/JSMR.UI.Blazor/Components/JProduct.razor +++ b/JSMR.UI.Blazor/Components/JProduct.razor @@ -1,12 +1,30 @@ @using JSMR.Application.VoiceWorks.Queries.Search @using JSMR.UI.Blazor.Services +@using System.Globalization
- +
@Product.ProductName
+
+ + @Product.Maker + @foreach (var creator in Product.Creators) + { + @creator.Name + } + +
@Product.Description
@foreach (var tag in Product.Tags) @@ -16,10 +34,52 @@
+
+ + @GetSomething(Product) +
+
+ + @((Product.WishlistCount ?? 0).ToString("n0")) +
+ @if (Product.SalesDate is not null) + { +
+ + @((Product.Downloads ?? 0).ToString("n0")) +
+ }
@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"; + } } diff --git a/JSMR.UI.Blazor/Pages/VoiceWorks.razor b/JSMR.UI.Blazor/Pages/VoiceWorks.razor index ad69973..6c66569 100644 --- a/JSMR.UI.Blazor/Pages/VoiceWorks.razor +++ b/JSMR.UI.Blazor/Pages/VoiceWorks.razor @@ -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 @@ Voice Works -

VoiceWorks

+

Voice Works

- + + +@if (searchResults is not null) +{ + +} @code { - VoiceWorkSearchResult[]? items; + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 100; + + SearchResult? 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); } } \ No newline at end of file diff --git a/JSMR.UI.Blazor/Services/ImageUrlProvider.cs b/JSMR.UI.Blazor/Services/ImageUrlProvider.cs index 66ec006..38a92b4 100644 --- a/JSMR.UI.Blazor/Services/ImageUrlProvider.cs +++ b/JSMR.UI.Blazor/Services/ImageUrlProvider.cs @@ -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"; diff --git a/JSMR.UI.Blazor/wwwroot/css/app.css b/JSMR.UI.Blazor/wwwroot/css/app.css index 8c9dce4..9a34f43 100644 --- a/JSMR.UI.Blazor/wwwroot/css/app.css +++ b/JSMR.UI.Blazor/wwwroot/css/app.css @@ -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"); } \ No newline at end of file diff --git a/JSMR.UI.Blazor/wwwroot/css/theme-frozen.css b/JSMR.UI.Blazor/wwwroot/css/theme-frozen.css index 43a87d6..f56b902 100644 --- a/JSMR.UI.Blazor/wwwroot/css/theme-frozen.css +++ b/JSMR.UI.Blazor/wwwroot/css/theme-frozen.css @@ -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); diff --git a/JSMR.UI.Blazor/wwwroot/svg/bag-fill.svg b/JSMR.UI.Blazor/wwwroot/svg/bag-fill.svg new file mode 100644 index 0000000..aa5c83c --- /dev/null +++ b/JSMR.UI.Blazor/wwwroot/svg/bag-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/JSMR.UI.Blazor/wwwroot/svg/bag.svg b/JSMR.UI.Blazor/wwwroot/svg/bag.svg new file mode 100644 index 0000000..7545d3b --- /dev/null +++ b/JSMR.UI.Blazor/wwwroot/svg/bag.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/JSMR.UI.Blazor/wwwroot/svg/calendar.svg b/JSMR.UI.Blazor/wwwroot/svg/calendar.svg new file mode 100644 index 0000000..8f180c9 --- /dev/null +++ b/JSMR.UI.Blazor/wwwroot/svg/calendar.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file