Merge pull request 'CSS/UI fixes for cosplayers page and image previewer' (#67) from feature/CSS-UI-Fixes into develop

Reviewed-on: #67
Reviewed-by: Samuele Lorefice <aironenerowork@gmail.com>
This commit was merged in pull request #67.
This commit is contained in:
2026-07-09 21:31:52 +00:00
11 changed files with 90 additions and 10 deletions

View File

@@ -41,6 +41,10 @@ public class AssetPreviewDto {
/// </summary>
public List<string>? CosplayerNames { get; set; }
/// <summary>
/// Gets or sets the list of cosplayer IDs associated with the asset, parallel to <see cref="CosplayerNames"/>.
/// </summary>
public List<Guid>? CosplayerIds { get; set; }
/// <summary>
/// Gets or sets the original filename of the asset.
/// </summary>
public string? FileName { get; set; }

View File

@@ -50,6 +50,11 @@ public static class AssetsMapper {
.Where(a => a.PersonOwner != null)
.Select(a => a.PersonOwner!.Name)
.Distinct()
.ToList(),
CosplayerIds = asset.Albums?
.Where(a => a.PersonOwner != null)
.Select(a => a.PersonOwner!.Id)
.Distinct()
.ToList()
};

View File

@@ -107,10 +107,10 @@
Filename="@(currentPreview?.FileName)"
OnNavigate="NavigatePreview"
OnClose="ClosePreview">
@if (!string.IsNullOrEmpty(album.PersonName)) {
<span class="preview-info-cosplayer">@album.PersonName</span>
@if (!string.IsNullOrEmpty(album.PersonName) && album.Person.HasValue) {
<a class="preview-info-cosplayer" href="/cosplayer/@album.Person.Value" @onclick:stopPropagation="true">@album.PersonName</a>
}
<span class="preview-info-album">@album.Name</span>
<a class="preview-info-album" href="/albums/@album.Id" @onclick:stopPropagation="true">@album.Name</a>
</ImagePreview>
}

View File

@@ -66,8 +66,10 @@
</div>
</div>
} else {
<div class="cosplayer-card-fallback">
<i class="bi bi-person-circle"></i>
<div class="cosplayer-card-img-wrap">
<div class="cosplayer-card-fallback">
<i class="bi bi-person-circle"></i>
</div>
</div>
}
<div class="cosplayer-card-info">

View File

@@ -96,7 +96,15 @@
OnNavigate="NavigatePreview"
OnClose="ClosePreview">
@if (selectedAsset.CosplayerNames?.Count > 0) {
<span class="preview-info-cosplayer">@string.Join(", ", selectedAsset.CosplayerNames)</span>
@for (var cIdx = 0; cIdx < selectedAsset.CosplayerNames.Count; cIdx++) {
var cName = selectedAsset.CosplayerNames[cIdx];
var cId = selectedAsset.CosplayerIds != null && cIdx < selectedAsset.CosplayerIds.Count ? selectedAsset.CosplayerIds[cIdx] : (Guid?)null;
@if (cId.HasValue) {
<a class="preview-info-cosplayer" href="/cosplayer/@cId.Value" @onclick:stopPropagation="true">@cName</a>
} else {
<span class="preview-info-cosplayer">@cName</span>
}
}
}
@if (selectedAsset.AlbumNames?.Count > 0) {
var albumIdx = 0;

View File

@@ -21,8 +21,8 @@
}
<div class="album-card-info">
<span class="album-card-title">@Album.Name</span>
@if (!string.IsNullOrEmpty(Album.PersonName)) {
<span class="album-card-person">@Album.PersonName</span>
@if (!string.IsNullOrEmpty(Album.PersonName) && Album.Person.HasValue) {
<a class="album-card-person" href="/cosplayer/@Album.Person.Value" @onclick:stopPropagation="true">@Album.PersonName</a>
}
<span class="album-card-count">@Album.AssetCount items</span>
</div>

View File

@@ -1,9 +1,12 @@
@using Butter.Dtos.Album
@using Butter.Dtos.Person
@using Microsoft.JSInterop
@using MilkStream.Client.Services
@implements IAsyncDisposable
@inject AlbumService albumService
@inject PersonService personService
@inject IJSRuntime JSRuntime
@if (Show) {
<div class="modal-overlay" @onclick="OnClose">
@@ -75,6 +78,17 @@
List<PersonPreviewDto>? people;
List<Guid>? albumAssetIds;
bool showAssetPicker;
bool _prevShow;
protected override async Task OnAfterRenderAsync(bool firstRender) {
if (Show != _prevShow) {
_prevShow = Show;
if (Show)
await JSRuntime.InvokeVoidAsync("masonryObserver.lockBodyScroll");
else
await JSRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
}
}
protected override async Task OnParametersSetAsync() {
if (Show && people == null) {
@@ -125,6 +139,14 @@
}
async Task OnClose() {
await JSRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
_prevShow = false;
await OnClosed.InvokeAsync();
}
/// <inheritdoc />
public async ValueTask DisposeAsync() {
if (Show)
await JSRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
}
}

View File

@@ -107,6 +107,12 @@
font-size: 0.95rem;
font-weight: 600;
line-height: 1.4;
text-decoration: none;
}
::deep .preview-info-cosplayer:hover {
color: #fff;
text-decoration: underline;
}
::deep .preview-info-album {

View File

@@ -1,9 +1,12 @@
@using Butter.Dtos.Person
@using Butter.Dtos.Album
@using Microsoft.JSInterop
@using MilkStream.Client.Services
@implements IAsyncDisposable
@inject PersonService personService
@inject AlbumService albumService
@inject IJSRuntime JSRuntime
@if (Show) {
<div class="modal-overlay" @onclick="OnClose">
@@ -124,6 +127,17 @@
List<AlbumPreviewDto>? unassignedAlbums;
HashSet<Guid> selectedAlbumIds = [];
Guid? createdPersonId;
bool _prevShow;
protected override async Task OnAfterRenderAsync(bool firstRender) {
if (Show != _prevShow) {
_prevShow = Show;
if (Show)
await JSRuntime.InvokeVoidAsync("masonryObserver.lockBodyScroll");
else
await JSRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
}
}
protected override async Task OnParametersSetAsync() {
if (Show && EditPerson != null) {
@@ -218,6 +232,14 @@
}
async Task OnClose() {
await JSRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
_prevShow = false;
await OnClosed.InvokeAsync();
}
/// <inheritdoc />
public async ValueTask DisposeAsync() {
if (Show)
await JSRuntime.InvokeVoidAsync("masonryObserver.unlockBodyScroll");
}
}

View File

@@ -11866,6 +11866,16 @@ textarea.form-control-lg {
justify-content: center;
}
@media (max-width: 576px) {
.modal-overlay {
align-items: flex-start;
padding-top: max(8px, env(safe-area-inset-top));
}
.modal-content {
max-height: 85vh;
}
}
.modal-content {
background: var(--bs-body-bg, #222);
color: var(--bs-body-color, #eee);

View File

@@ -11,7 +11,8 @@
# CorsAllowedOrigins must be the URL the *browser* uses to reach MilkStream.
# Use semicolons to specify multiple origins (e.g. http://host1;http://host2).
# Override this with your server's hostname/IP if not running on localhost.
- CorsAllowedOrigins=http://localhost:8080
#- CorsAllowedOrigins=http://localhost:8080
- CorsAllowedOrigins=*
ports:
- "5162:8080"
depends_on:
@@ -55,7 +56,7 @@
environment:
# LactoseBaseUrl must be the address the *browser* can reach Lactose at.
# Override this with your server's hostname/IP if not running on localhost.
- LactoseBaseUrl=http://localhost:5162/
- LactoseBaseUrl=http://192.168.50.100:5162/
ports:
- "8080:8080"
restart: unless-stopped