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 (
|
var tagRows = await (
|
||||||
from voiceWorkTag in context.VoiceWorkTags.AsNoTracking()
|
from voiceWorkTag in context.VoiceWorkTags.AsNoTracking()
|
||||||
join tag in context.Tags.AsNoTracking() on voiceWorkTag.TagId equals tag.TagId
|
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)
|
where voiceWorkIds.Contains(voiceWorkTag.VoiceWorkId)
|
||||||
orderby voiceWorkTag.VoiceWorkId, voiceWorkTag.Position
|
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);
|
).ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return tagRows
|
return tagRows
|
||||||
.GroupBy(r => r.VoiceWorkId)
|
.GroupBy(r => r.VoiceWorkId)
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
g => g.Key,
|
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="@ContainerClassees">
|
||||||
<div class="j-image-overlay"></div>
|
<div class="@OverlayClasses"></div>
|
||||||
<img class="@ImageClasses" loading="@LoadingAttribute" src="@Source" @onload="OnImageLoaded">
|
<img class="@ImageClasses" loading="@LoadingAttribute" src="@Source" @onload="OnImageLoaded">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -16,6 +16,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string? ContainerClass { get; set; }
|
public string? ContainerClass { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string? OverlayClass { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string? ImageClass { get; set; }
|
public string? ImageClass { get; set; }
|
||||||
|
|
||||||
@@ -23,6 +26,7 @@
|
|||||||
private string? _lastSource;
|
private string? _lastSource;
|
||||||
|
|
||||||
private string ContainerClassees => GetContainerClasses();
|
private string ContainerClassees => GetContainerClasses();
|
||||||
|
private string OverlayClasses => GetOverlayClasses();
|
||||||
private string ImageClasses => GetImageClasses();
|
private string ImageClasses => GetImageClasses();
|
||||||
|
|
||||||
private string? LoadingAttribute => LazyLoading ? "lazy" : null;
|
private string? LoadingAttribute => LazyLoading ? "lazy" : null;
|
||||||
@@ -54,6 +58,24 @@
|
|||||||
return string.Join(" ", classNames);
|
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()
|
private string GetImageClasses()
|
||||||
{
|
{
|
||||||
List<string> classNames = ["j-image"];
|
List<string> classNames = ["j-image"];
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
@using JSMR.Application.VoiceWorks.Queries.Search
|
@using JSMR.Application.VoiceWorks.Queries.Search
|
||||||
@using JSMR.UI.Blazor.Services
|
@using JSMR.UI.Blazor.Services
|
||||||
|
@using System.Globalization
|
||||||
|
|
||||||
<div class="j-card j-voice-work-card">
|
<div class="j-card j-voice-work-card">
|
||||||
<div class="j-voice-work-image-container">
|
<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>
|
||||||
<div class="j-voice-work-content">
|
<div class="j-voice-work-content">
|
||||||
<div class="j-product-title">@Product.ProductName</div>
|
<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-product-description">@Product.Description</div>
|
||||||
<div class="j-tags">
|
<div class="j-tags">
|
||||||
@foreach (var tag in Product.Tags)
|
@foreach (var tag in Product.Tags)
|
||||||
@@ -16,10 +34,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="j-voice-work-info">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public required VoiceWorkSearchResult Product { get; set; }
|
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"
|
@page "/voiceworks"
|
||||||
|
@using JSMR.Application.Common.Search
|
||||||
@using JSMR.Application.VoiceWorks.Queries.Search
|
@using JSMR.Application.VoiceWorks.Queries.Search
|
||||||
@using JSMR.UI.Blazor.Components
|
@using JSMR.UI.Blazor.Components
|
||||||
@using JSMR.UI.Blazor.Services
|
@using JSMR.UI.Blazor.Services
|
||||||
@@ -8,19 +9,59 @@
|
|||||||
|
|
||||||
<h3>Voice Works</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 {
|
@code {
|
||||||
VoiceWorkSearchResult[]? items;
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 100;
|
||||||
|
|
||||||
|
SearchResult<VoiceWorkSearchResult>? searchResults;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
await UpdateDataAsync(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateDataAsync(bool resetPageNumber)
|
||||||
|
{
|
||||||
|
if (resetPageNumber)
|
||||||
|
PageNumber = 1;
|
||||||
|
|
||||||
SearchVoiceWorksRequest request = new(
|
SearchVoiceWorksRequest request = new(
|
||||||
Options: 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 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)
|
public static string GetImageURL(string? productId, bool hasImage, DateTime? salesDate, string size)
|
||||||
{
|
{
|
||||||
string folder = "modpub";
|
string folder = "modpub";
|
||||||
|
|||||||
@@ -355,6 +355,10 @@ code {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.j-voice-work-image-overlay {
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.j-voice-work-image {
|
.j-voice-work-image {
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
}
|
}
|
||||||
@@ -377,6 +381,11 @@ code {
|
|||||||
text-shadow: 1px 1px 2px black;
|
text-shadow: 1px 1px 2px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.j-product-contributors {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: "Poppins", "M+ 1p";
|
||||||
|
}
|
||||||
|
|
||||||
.j-product-description {
|
.j-product-description {
|
||||||
/* color: #7C8099; */
|
/* color: #7C8099; */
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -389,15 +398,96 @@ code {
|
|||||||
|
|
||||||
.j-voice-work-info {
|
.j-voice-work-info {
|
||||||
width: 240px;
|
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 {
|
.j-voice-work-card > .j-voice-work-info {
|
||||||
flex-shrink: 0;
|
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 */
|
/* Tags */
|
||||||
.j-tags {
|
.j-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: .75rem .5rem;
|
||||||
flex-wrap: wrap;
|
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-text-color: rgb(210,220,230);
|
||||||
--tag-border: none;
|
--tag-border: none;
|
||||||
--tag-border-radius: 0.8em;
|
--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-margin: 1em 0.5em 0 0;
|
||||||
--tag-hover-background-color: #596f7e;
|
--tag-hover-background-color: #596f7e;
|
||||||
--tag-hover-text-color: rgb(240,250,254);
|
--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