Added more UI styling. Updated voice work search provider to send back English tag names, if applicable.
This commit is contained in:
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
3
JSMR.UI.Blazor/wwwroot/svg/bag-fill.svg
Normal file
3
JSMR.UI.Blazor/wwwroot/svg/bag-fill.svg
Normal 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 |
3
JSMR.UI.Blazor/wwwroot/svg/bag.svg
Normal file
3
JSMR.UI.Blazor/wwwroot/svg/bag.svg
Normal 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 |
4
JSMR.UI.Blazor/wwwroot/svg/calendar.svg
Normal file
4
JSMR.UI.Blazor/wwwroot/svg/calendar.svg
Normal 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 |
Reference in New Issue
Block a user