Updated CI for Blazor WebAssembly. Added styles for product cards.
All checks were successful
ci / build-test (push) Successful in 2m16s
ci / publish-image (push) Has been skipped

This commit is contained in:
2025-11-15 02:34:15 -05:00
parent 76284f2fab
commit 634050c06f
13 changed files with 289 additions and 94 deletions

View File

@@ -36,6 +36,9 @@ jobs:
- name: Docker sanity (ensures socket mount is working) - name: Docker sanity (ensures socket mount is working)
run: docker version run: docker version
- name: Install WASM workload
run: dotnet workload install wasm-tools --skip-sign-check
- run: dotnet restore - run: dotnet restore
- run: dotnet build --configuration Release --no-restore - run: dotnet build --configuration Release --no-restore
- run: dotnet test --configuration Release --no-build --logger "trx;LogFileName=test-results.trx" - run: dotnet test --configuration Release --no-build --logger "trx;LogFileName=test-results.trx"

View File

@@ -7,5 +7,6 @@ public enum VoiceWorkSortField
Downloads, Downloads,
WishlistCount, WishlistCount,
SalesToWishlistRatio, SalesToWishlistRatio,
StarRating StarRating,
FavoriteCircle
} }

View File

@@ -324,6 +324,7 @@ public class VoiceWorkSearchProvider(AppDbContext context, IVoiceWorkFullTextSea
VoiceWorkSortField.Downloads => x => x.VoiceWork.Downloads ?? 0, VoiceWorkSortField.Downloads => x => x.VoiceWork.Downloads ?? 0,
VoiceWorkSortField.WishlistCount => x => x.VoiceWork.WishlistCount ?? 0, VoiceWorkSortField.WishlistCount => x => x.VoiceWork.WishlistCount ?? 0,
VoiceWorkSortField.StarRating => x => x.VoiceWork.StarRating ?? 0, VoiceWorkSortField.StarRating => x => x.VoiceWork.StarRating ?? 0,
VoiceWorkSortField.FavoriteCircle => x => !x.Circle.Favorite,
_ => x => x.VoiceWork.ProductId _ => x => x.VoiceWork.ProductId
}; };

View File

@@ -0,0 +1,25 @@
@using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Services
<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>
</div>
<div class="j-voice-work-content">
<div class="j-product-title">@Product.ProductName</div>
<div class="j-product-description">@Product.Description</div>
<div class="j-tags">
@foreach (var tag in Product.Tags)
{
<div class="j-tag">@tag.Name</div>
}
</div>
</div>
<div class="j-voice-work-info">
</div>
</div>
@code {
[Parameter]
public required VoiceWorkSearchResult Product { get; set; }
}

View File

@@ -0,0 +1,24 @@
@using JSMR.Application.VoiceWorks.Queries.Search
@if (Products is null)
{
<p>Loading…</p>
}
else if (Products.Length == 0)
{
<p>No results.</p>
}
else
{
<div class="j-product-items-container">
@foreach (var product in Products)
{
<JProduct Product="@product"></JProduct>
}
</div>
}
@code {
[Parameter]
public VoiceWorkSearchResult[]? Products { get; set; }
}

View File

@@ -0,0 +1,17 @@
namespace JSMR.UI.Blazor.Enums;
/// <summary>Image size selector for DLsite assets.</summary>
public enum ImageSize
{
/// <summary>100×100 square thumbnail (DLsite: "sam").</summary>
Thumb100,
/// <summary>240×240 square (DLsite: "main_240x240").</summary>
Square240,
/// <summary>300×300 square (DLsite: "main_300x300").</summary>
Square300,
/// <summary>Native “main” image (typically ~560×420).</summary>
Main
}

View File

@@ -40,11 +40,13 @@ else
@foreach (var item in searchResults.Items) @foreach (var item in searchResults.Items)
{ {
<div class="circle-item"> <div class="circle-item">
@* <JImage @key="item.CircleId" ContainerClass="j-circle-image-container" ImageClass="j-circle-image" Source="@ImageUrlProvider.GetImageURL(item.LatestProductId, item.LatestVoiceWorkHasImage ?? false, item.LatestVoiceWorkSalesDate, "main")" /> *@ <JImage @key="item.CircleId" ContainerClass="j-circle-image-container-2" ImageClass="j-circle-image-2" Source="@ImageUrlProvider.GetImageURL(item.LatestProductId, item.LatestVoiceWorkHasImage ?? false, item.LatestVoiceWorkSalesDate, "main")" />
<div class="fdfs"> <div class="fdfs">
<div class="circle-name">@item.Name</div> <div class="circle-name">@item.Name</div>
<div>@item.MakerId</div> <div>@item.MakerId</div>
</div> </div>
@if (item.Favorite) @if (item.Favorite)
{ {
<MudChip T="string" Label="true" Color="Color.Info" Style="width: 100%" Variant="Variant.Outlined">Favorite</MudChip> <MudChip T="string" Label="true" Color="Color.Info" Style="width: 100%" Variant="Variant.Outlined">Favorite</MudChip>
@@ -195,7 +197,7 @@ else
.circle-item { .circle-item {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 1rem; padding: .5rem;
border-width: 2px; border-width: 2px;
border-top-color: #353B4C; border-top-color: #353B4C;
border-left-color: #212630; border-left-color: #212630;
@@ -204,8 +206,23 @@ else
border-radius: 30px; border-radius: 30px;
background-image: linear-gradient(0deg, #1C2029, #1C1F28); background-image: linear-gradient(0deg, #1C2029, #1C1F28);
display: grid; display: grid;
grid-template-columns: 1fr 100px 100px 100px 100px; grid-template-columns: auto 1fr 100px 100px 100px 100px;
grid-column-gap: 2rem; grid-column-gap: 1rem;
}
.j-circle-image-container-2 {
height: auto;
}
.j-circle-image-2 {
display: block;
object-fit: cover;
background-color: black;
border: 1px solid #949494;
box-sizing: border-box;
border-radius: 100%;
width: 70px;
height: 70px;
} }
.circle-name { .circle-name {

View File

@@ -1,70 +1,84 @@
@page "/" @page "/"
@inject VoiceWorksClient Client @inject VoiceWorksClient Client
@using JSMR.Application.VoiceWorks.Queries.Search @using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Components
@using JSMR.UI.Blazor.Services @using JSMR.UI.Blazor.Services
<PageTitle>Home</PageTitle> <PageTitle>Home</PageTitle>
<MudTabs Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-6"> <MudTabs Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-6">
<MudTabPanel Text="Available" Icon="@Icons.Material.Filled.Home"> <MudTabPanel Text="Available" Icon="@Icons.Material.Filled.Home">
@if (availableVoiceWorks is null) <JProductCollection Products="availableVoiceWorks"></JProductCollection>
{
<p>Loading…</p>
}
else if (availableVoiceWorks.Length == 0)
{
<p>No results.</p>
}
else
{
<ul>
@foreach (var v in availableVoiceWorks)
{
<li>@v.ProductId @v.ProductName</li>
}
</ul>
}
</MudTabPanel> </MudTabPanel>
<MudTabPanel Text="Upcoming" Icon="@Icons.Material.Filled.ArrowUpward"> <MudTabPanel Text="Upcoming" Icon="@Icons.Material.Filled.ArrowUpward">
@if (upcomingVoiceWorks is null) <JProductCollection Products="upcomingVoiceWorks"></JProductCollection>
{
<p>Loading…</p>
}
else if (upcomingVoiceWorks.Length == 0)
{
<p>No results.</p>
}
else
{
<ul>
@foreach (var v in upcomingVoiceWorks)
{
<li>@v.ProductId @v.ProductName</li>
}
</ul>
}
</MudTabPanel> </MudTabPanel>
<MudTabPanel Text="Announcements" Icon="@Icons.Material.Filled.Home"> <MudTabPanel Text="Announcements" Icon="@Icons.Material.Filled.Home">
@if (announcedVoiceWorks is null) <JProductCollection Products="announcedVoiceWorks"></JProductCollection>
{
<p>Loading…</p>
}
else if (announcedVoiceWorks.Length == 0)
{
<p>No results.</p>
}
else
{
<ul>
@foreach (var v in announcedVoiceWorks)
{
<li>@v.ProductId @v.ProductName</li>
}
</ul>
}
</MudTabPanel> </MudTabPanel>
</MudTabs> </MudTabs>
<style>
.j-product-items-container {
display: flex;
flex-direction: column;
gap: 2rem;
}
.j-voice-work-card {
display: flex;
gap: 1rem;
background-image: linear-gradient(0deg, rgb(30, 53, 69), rgb(39, 59, 73));
border-color: rgb(63, 78, 88);
background-image: linear-gradient(0deg, rgb(30, 53, 69), rgb(57, 79, 94));
}
.j-voice-work-image-container {
width: 240px;
width: 300px;
}
.j-voice-work-card > .j-voice-work-image-container {
flex-shrink: 0;
}
.j-voice-work-image {
border-radius: 20px;
}
.j-voice-work-content {
display: flex;
flex-direction: column;
gap: .5rem;
}
.j-voice-work-card > .j-voice-work-content {
flex-grow: 1;
}
.j-product-title {
font-size: 1.25rem;
font-weight: 600;
font-family: "Poppins", "M+ 1p";
color: #d2dce6;
text-shadow: 1px 1px 2px black;
}
.j-product-description {
/* color: #7C8099; */
font-size: 1rem;
font-family: "Poppins", "M+ 1p";
}
.j-voice-work-info {
width: 240px;
}
.j-voice-work-card > .j-voice-work-info {
flex-shrink: 0;
}
</style>
@code { @code {
VoiceWorkSearchResult[]? availableVoiceWorks; VoiceWorkSearchResult[]? availableVoiceWorks;
VoiceWorkSearchResult[]? upcomingVoiceWorks; VoiceWorkSearchResult[]? upcomingVoiceWorks;
@@ -143,7 +157,7 @@
}, },
SortOptions = SortOptions =
[ [
//new(VoiceWorkSortField.Fa, Application.Common.Search.SortDirection.Ascending), new(VoiceWorkSortField.FavoriteCircle, Application.Common.Search.SortDirection.Ascending),
new(VoiceWorkSortField.WishlistCount, Application.Common.Search.SortDirection.Descending) new(VoiceWorkSortField.WishlistCount, Application.Common.Search.SortDirection.Descending)
] ]
} }

View File

@@ -1,5 +1,6 @@
@page "/voiceworks" @page "/voiceworks"
@using JSMR.Application.VoiceWorks.Queries.Search @using JSMR.Application.VoiceWorks.Queries.Search
@using JSMR.UI.Blazor.Components
@using JSMR.UI.Blazor.Services @using JSMR.UI.Blazor.Services
@inject VoiceWorksClient Client @inject VoiceWorksClient Client
@@ -7,26 +8,10 @@
<h3>VoiceWorks</h3> <h3>VoiceWorks</h3>
@if (items is null) <JProductCollection Products="items"></JProductCollection>
{
<p>Loading…</p>
}
else if (items.Count == 0)
{
<p>No results.</p>
}
else
{
<ul>
@foreach (var v in items)
{
<li>@v.ProductId @v.ProductName</li>
}
</ul>
}
@code { @code {
List<VoiceWorkSearchResult>? items; VoiceWorkSearchResult[]? items;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@@ -36,12 +21,6 @@ else
var result = await Client.SearchAsync(request); var result = await Client.SearchAsync(request);
// if (result.Ok) items = result?.Results.Items ?? [];
// {
// items = result.Value!.Results.Items.ToList();
// }
//items = result.Value?.Results.Items ?? [];
items = result.Results.Items.ToList();
} }
} }

View File

@@ -41,19 +41,15 @@ public static class ImageUrlProvider
var imageUrlTemplate = "//img.dlsite.jp/[folder]/images2/[imageType1]/[imageWorkType]/[fullRoundedProductId]/[productId][imageType2]_img_[imageSize].jpg"; var imageUrlTemplate = "//img.dlsite.jp/[folder]/images2/[imageType1]/[imageWorkType]/[fullRoundedProductId]/[productId][imageType2]_img_[imageSize].jpg";
var productIdWithoutPrefixString = productId.Substring(2); string productIdWithoutPrefixString = productId.Substring(2);
int productIdWithoutPrefix = Convert.ToInt32(productId.Substring(2)); int productIdWithoutPrefix = Convert.ToInt32(productId.Substring(2));
string productIdPrefix = productId.Substring(0, 2); string productIdPrefix = productId.Substring(0, 2);
double something = (double)((productIdWithoutPrefix / 1000) * 1000);
int roundedProductId = (int)Math.Round(Math.Ceiling((double)productIdWithoutPrefix / 1000) * 1000); int roundedProductId = (int)Math.Round(Math.Ceiling((double)productIdWithoutPrefix / 1000) * 1000);
//string actualRoundedProductId = ("000000" + roundedProductId.ToString()).Substring(roundedProductId.ToString().Length); int productIdWithPrefixStringLength = productIdWithoutPrefixString.Length;
//string fullRoundedProductId = productIdPrefix + actualRoundedProductId; int zeroPadLength = productIdWithPrefixStringLength - roundedProductId.ToString().Length;
var productIdWithPrefixStringLength = productIdWithoutPrefixString.Length;
var zeroPadLength = productIdWithPrefixStringLength - roundedProductId.ToString().Length;
var fullRoundedProductId = productIdPrefix.PadRight(productIdPrefix.Length + zeroPadLength, '0') + roundedProductId; var fullRoundedProductId = productIdPrefix.PadRight(productIdPrefix.Length + zeroPadLength, '0') + roundedProductId;

View File

@@ -305,3 +305,99 @@ code {
.star_rating.mini.star_45::before { .star_rating.mini.star_45::before {
background-position: 0 -16px; background-position: 0 -16px;
} }
/* Card */
.j-card {
padding-top: var(--card-padding-top);
padding-bottom: var(--card-padding-bottom);
padding-left: var(--card-padding-left);
padding-right: var(--card-padding-right);
border-width: var(--card-border-width);
border-top-color: var(--card-border-top-color);
border-left-color: var(--card-border-left-color);
border-right-color: var(--card-border-right-color);
border-bottom-color: var(--card-border-bottom-color);
border-radius: var(--card-border-radius);
background-image: var(--card-background-image);
}
/* Image */
.j-image-container {
}
.j-image {
width: 100%;
}
/* Product */
.j-product-items-container {
display: flex;
flex-direction: column;
gap: 2rem;
}
.j-voice-work-card {
display: flex;
gap: 1rem;
background-image: linear-gradient(0deg, rgb(30, 53, 69), rgb(39, 59, 73));
border-color: rgb(63, 78, 88);
background-image: linear-gradient(0deg, rgb(30, 53, 69), rgb(57, 79, 94));
}
.j-voice-work-image-container {
width: 240px;
width: 300px;
}
.j-voice-work-card > .j-voice-work-image-container {
flex-shrink: 0;
}
.j-voice-work-image {
border-radius: 20px;
}
.j-voice-work-content {
display: flex;
flex-direction: column;
gap: .5rem;
}
.j-voice-work-card > .j-voice-work-content {
flex-grow: 1;
}
.j-product-title {
font-size: 1.25rem;
font-weight: 600;
font-family: "Poppins", "M+ 1p";
color: #d2dce6;
text-shadow: 1px 1px 2px black;
}
.j-product-description {
/* color: #7C8099; */
font-size: 1rem;
font-family: "Poppins", "M+ 1p";
}
.j-voice-work-card > .j-voice-work-content > .j-product-description {
flex-grow: 1;
}
.j-voice-work-info {
width: 240px;
}
.j-voice-work-card > .j-voice-work-info {
flex-shrink: 0;
}
/* Tags */
.j-tags {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}

View File

@@ -1,7 +1,7 @@
:root { :root {
--font-family: 'Poppins'; --font-family: 'Poppins';
--background-color: #131419; --background-color-backup: #131419;
--background-color-original: rgb(16, 36, 50); --background-color: rgb(16, 36, 50);
--input-background-color: rgb(0,20,34); --input-background-color: rgb(0,20,34);
--input-border-color: #304562; --input-border-color: #304562;
--primary-text-color: rgb(180,200, 214); --primary-text-color: rgb(180,200, 214);
@@ -22,4 +22,26 @@
--product-footer-text-color: rgb(220,230,234); --product-footer-text-color: rgb(220,230,234);
--expected-date-text-color: #ffe073; --expected-date-text-color: #ffe073;
--planned-date-text-color: #73bdff; --planned-date-text-color: #73bdff;
--card-padding-top: .5rem;
--card-padding-bottom: .5rem;
--card-padding-left: .5rem;
--card-padding-right: .5rem;
--card-background-image: linear-gradient(0deg, #1C2029, #1C1F28);
--card-border-radius: 30px;
--card-border-width: 2px;
--card-border-top-color: #353B4C;
--card-border-left-color: #212630;
--card-border-right-color: #212531;
--card-border-bottom-color: #212530;
} }
/*
padding: .5rem;
border-width: 2px;
border-top-color: #353B4C;
border-left-color: #212630;
border-right-color: #212531;
border-bottom-color: #212530;
border-radius: 30px;
background-image: linear-gradient(0deg, #1C2029, #1C1F28);
*/

View File

@@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 18
VisualStudioVersion = 17.14.36408.4 VisualStudioVersion = 18.0.11205.157 d18.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.Domain", "JSMR.Domain\JSMR.Domain.csproj", "{BC16F228-63B0-4EE6-9B96-19A38A31C125}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.Domain", "JSMR.Domain\JSMR.Domain.csproj", "{BC16F228-63B0-4EE6-9B96-19A38A31C125}"
EndProject EndProject