Merge branch 'p8-w12-release-track-normalize' into dev

This commit is contained in:
daniel-c-harvey
2026-06-11 15:55:10 -04:00
35 changed files with 1276 additions and 302 deletions
+6 -6
View File
@@ -89,22 +89,22 @@ public class TrackClient
: ApiResult<TrackDto?>.CreateFailResult("Failed to deserialize response");
}
public async Task<ApiResult<List<AlbumSummaryDto>>> GetAlbums()
public async Task<ApiResult<List<ReleaseDto>>> GetAlbums()
{
var response = await _http.GetAsync("api/track/albums");
if (!response.IsSuccessStatusCode)
return ApiResult<List<AlbumSummaryDto>>.CreateFailResult($"HTTP {(int)response.StatusCode}");
return ApiResult<List<ReleaseDto>>.CreateFailResult($"HTTP {(int)response.StatusCode}");
var json = await response.Content.ReadAsStringAsync();
var albums = JsonSerializer.Deserialize<List<AlbumSummaryDto>>(json, new JsonSerializerOptions
var releases = JsonSerializer.Deserialize<List<ReleaseDto>>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return albums is not null
? ApiResult<List<AlbumSummaryDto>>.CreatePassResult(albums)
: ApiResult<List<AlbumSummaryDto>>.CreateFailResult("Failed to deserialize response");
return releases is not null
? ApiResult<List<ReleaseDto>>.CreatePassResult(releases)
: ApiResult<List<ReleaseDto>>.CreateFailResult("Failed to deserialize response");
}
public async Task<ApiResult<List<GenreSummaryDto>>> GetGenres()
@@ -13,26 +13,26 @@
</a>
<MudText Typo="Typo.subtitle2" Class="track-meta-sep"> - </MudText>
<MudText Typo="Typo.caption" Class="track-meta-artist text-truncate">
@Track.Artist
@Track.Release?.Artist
</MudText>
</div>
<div class="track-meta-accents">
@if (!string.IsNullOrEmpty(Track.Genre))
@if (!string.IsNullOrEmpty(Track.Release?.Genre))
{
<MudChip T="string"
Size="Size.Small"
Variant="Variant.Outlined"
Color="Color.Tertiary"
Class="deepdrft-genre-chip">
@Track.Genre
@Track.Release.Genre
</MudChip>
}
@if (Track.ReleaseDate.HasValue)
@if (Track.Release?.ReleaseDate.HasValue == true)
{
<MudText Typo="Typo.caption" Class="track-meta-year">
@Track.ReleaseDate.Value.Year
@Track.Release.ReleaseDate.Value.Year
</MudText>
}
</div>
@@ -4,7 +4,7 @@
<div class="np-title">@(Player?.CurrentTrack?.TrackName ?? "Nothing playing")</div>
<div class="np-sub">
@(Player?.CurrentTrack != null
? $"{Player.CurrentTrack.Artist} · {Player.CurrentTrack.Album ?? "Single"}"
? $"{Player.CurrentTrack.Release?.Artist} · {Player.CurrentTrack.Release?.Title ?? "Single"}"
: "Select a track to begin")
</div>
+27 -27
View File
@@ -1,7 +1,7 @@
@{
var hasLink = !string.IsNullOrEmpty(TrackModel?.EntryKey);
var trackHref = hasLink ? $"/track/{TrackModel!.EntryKey}" : null;
var hasArt = !string.IsNullOrEmpty(TrackModel?.ImagePath);
var hasArt = !string.IsNullOrEmpty(TrackModel?.Release?.ImagePath);
}
@if (ViewMode == GalleryViewMode.Grid)
@@ -13,9 +13,9 @@
@if (hasLink)
{
<a href="@trackHref" class="deepdrft-track-card-link">
@if (!string.IsNullOrEmpty(TrackModel?.ImagePath))
@if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
{
<div class="deepdrft-track-card-bg" style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.ImagePath)');">
<div class="deepdrft-track-card-bg" style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
</div>
}
else
@@ -24,9 +24,9 @@
}
</a>
}
else if (!string.IsNullOrEmpty(TrackModel?.ImagePath))
else if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
{
<div class="deepdrft-track-card-bg" style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.ImagePath)');">
<div class="deepdrft-track-card-bg" style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
</div>
}
else
@@ -47,7 +47,7 @@
<MudText Typo="Typo.caption"
Class="deepdrft-track-artist text-truncate mb-2">
@TrackModel?.Artist
@TrackModel?.Release?.Artist
</MudText>
</div>
</a>
@@ -62,38 +62,38 @@
<MudText Typo="Typo.caption"
Class="deepdrft-track-artist text-truncate mb-2">
@TrackModel?.Artist
@TrackModel?.Release?.Artist
</MudText>
</div>
}
<div class="deepdrft-track-info-middle">
@if (!string.IsNullOrEmpty(TrackModel?.Album))
@if (!string.IsNullOrEmpty(TrackModel?.Release?.Title))
{
<MudText Typo="Typo.caption"
Class="deepdrft-track-meta text-truncate">
@TrackModel.Album
@TrackModel.Release!.Title
</MudText>
}
@if (!string.IsNullOrEmpty(TrackModel?.Genre))
@if (!string.IsNullOrEmpty(TrackModel?.Release?.Genre))
{
<MudChip T="string"
Size="Size.Small"
Variant="Variant.Outlined"
Color="Color.Tertiary"
Class="deepdrft-genre-chip">
@TrackModel.Genre
@TrackModel.Release!.Genre
</MudChip>
}
</div>
<div class="deepdrft-track-info-bottom">
@if (TrackModel?.ReleaseDate.HasValue == true)
@if (TrackModel?.Release?.ReleaseDate.HasValue == true)
{
<MudText Typo="Typo.caption"
Class="deepdrft-track-meta">
@TrackModel.ReleaseDate.Value.Year
@TrackModel.Release!.ReleaseDate!.Value.Year
</MudText>
}
else
@@ -127,10 +127,10 @@ else
{
<a href="@trackHref" class="deepdrft-track-row-link">
@* art thumb *@
@if (!string.IsNullOrEmpty(TrackModel?.ImagePath))
@if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
{
<div class="deepdrft-track-row-thumb"
style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.ImagePath)');">
style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
</div>
}
else
@@ -141,7 +141,7 @@ else
@* text block *@
<div class="deepdrft-track-row-text">
<MudText Typo="Typo.subtitle2" Class="deepdrft-track-title text-truncate">
@TrackModel?.Artist
@TrackModel?.Release?.Artist
</MudText>
<MudText Typo="Typo.caption" Class="deepdrft-track-meta text-truncate">
@TrackModel?.TrackName
@@ -150,20 +150,20 @@ else
@* right metadata *@
<div class="deepdrft-track-row-meta">
@if (!string.IsNullOrEmpty(TrackModel?.Genre))
@if (!string.IsNullOrEmpty(TrackModel?.Release?.Genre))
{
<MudChip T="string"
Size="Size.Small"
Variant="Variant.Outlined"
Color="Color.Tertiary"
Class="deepdrft-genre-chip">
@TrackModel.Genre
@TrackModel.Release!.Genre
</MudChip>
}
@if (TrackModel?.ReleaseDate.HasValue == true)
@if (TrackModel?.Release?.ReleaseDate.HasValue == true)
{
<MudText Typo="Typo.caption" Class="deepdrft-track-meta">
@TrackModel.ReleaseDate.Value.Year
@TrackModel.Release!.ReleaseDate!.Value.Year
</MudText>
}
</div>
@@ -172,10 +172,10 @@ else
else
{
@* same structure without anchor *@
@if (!string.IsNullOrEmpty(TrackModel?.ImagePath))
@if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
{
<div class="deepdrft-track-row-thumb"
style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.ImagePath)');">
style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
</div>
}
else
@@ -184,27 +184,27 @@ else
}
<div class="deepdrft-track-row-text">
<MudText Typo="Typo.subtitle2" Class="deepdrft-track-title text-truncate">
@TrackModel?.Artist
@TrackModel?.Release?.Artist
</MudText>
<MudText Typo="Typo.caption" Class="deepdrft-track-meta text-truncate">
@TrackModel?.TrackName
</MudText>
</div>
<div class="deepdrft-track-row-meta">
@if (!string.IsNullOrEmpty(TrackModel?.Genre))
@if (!string.IsNullOrEmpty(TrackModel?.Release?.Genre))
{
<MudChip T="string"
Size="Size.Small"
Variant="Variant.Outlined"
Color="Color.Tertiary"
Class="deepdrft-genre-chip">
@TrackModel.Genre
@TrackModel.Release!.Genre
</MudChip>
}
@if (TrackModel?.ReleaseDate.HasValue == true)
@if (TrackModel?.Release?.ReleaseDate.HasValue == true)
{
<MudText Typo="Typo.caption" Class="deepdrft-track-meta">
@TrackModel.ReleaseDate.Value.Year
@TrackModel.Release!.ReleaseDate!.Value.Year
</MudText>
}
</div>
+4 -4
View File
@@ -33,11 +33,11 @@
<div class="album-card"
role="button"
tabindex="0"
@onclick="@(() => OpenAlbum(album.Album))">
@if (!string.IsNullOrEmpty(album.CoverImageKey))
@onclick="@(() => OpenAlbum(album.Title))">
@if (!string.IsNullOrEmpty(album.ImagePath))
{
<div class="album-card-cover"
style="background-image: url('api/image/@Uri.EscapeDataString(album.CoverImageKey)');">
style="background-image: url('api/image/@Uri.EscapeDataString(album.ImagePath)');">
</div>
}
else
@@ -47,7 +47,7 @@
<div class="album-card-body">
<MudText Typo="Typo.subtitle1" Class="album-card-title text-truncate">
@album.Album
@album.Title
</MudText>
<MudText Typo="Typo.caption" Class="album-card-count">
@album.TrackCount @(album.TrackCount == 1 ? "track" : "tracks")
@@ -10,7 +10,7 @@ public partial class AlbumsView : ComponentBase
[Inject] public required NavigationManager Navigation { get; set; }
private bool _loading = true;
private List<AlbumSummaryDto> _albums = [];
private List<ReleaseDto> _albums = [];
protected override async Task OnInitializedAsync()
{
+12 -10
View File
@@ -43,7 +43,9 @@ else if (ViewModel.Track is not null)
var isThisTrackPlaying = PlayerService.CurrentTrack?.Id == track.Id
&& PlayerService.IsPlaying
&& !PlayerService.IsPaused;
var hasMeta = track.Album is not null || track.Genre is not null || track.ReleaseDate is not null;
var release = track.Release;
var hasMeta = release is not null
&& (release.Title is not null || release.Genre is not null || release.ReleaseDate is not null);
<div class="deepdrft-track-detail-container">
@@ -54,7 +56,7 @@ else if (ViewModel.Track is not null)
<MudStack Row AlignItems="AlignItems.Start" Justify="Justify.SpaceBetween" Style="margin: 2rem 0 1.5rem;">
<div class="deepdrft-track-detail-masthead">
<MudText Typo="Typo.h3">@track.TrackName</MudText>
<MudText Typo="Typo.h6" Color="Color.Primary">@track.Artist</MudText>
<MudText Typo="Typo.h6" Color="Color.Primary">@release?.Artist</MudText>
</div>
<MudStack Row AlignItems="AlignItems.Center" Spacing="1">
@@ -64,10 +66,10 @@ else if (ViewModel.Track is not null)
</MudStack>
<div class="deepdrft-track-detail-cover">
@if (!string.IsNullOrEmpty(track.ImagePath))
@if (!string.IsNullOrEmpty(release?.ImagePath))
{
<MudPaper Elevation="2" Class="deepdrft-track-detail-cover-art"
Style="@($"background-image: url('api/image/{Uri.EscapeDataString(track.ImagePath)}');")" />
Style="@($"background-image: url('api/image/{Uri.EscapeDataString(release.ImagePath)}');")" />
}
else
{
@@ -82,31 +84,31 @@ else if (ViewModel.Track is not null)
<MudDivider />
<div class="deepdrft-track-detail-meta">
@if (track.Album is not null)
@if (release?.Title is not null)
{
<div>
<MudText Typo="Typo.overline">Album</MudText>
<MudText Typo="Typo.body1">@track.Album</MudText>
<MudText Typo="Typo.body1">@release.Title</MudText>
</div>
}
@if (track.Genre is not null)
@if (release?.Genre is not null)
{
<div>
<MudChip T="string"
Variant="Variant.Outlined"
Color="Color.Tertiary"
Class="deepdrft-genre-chip">
@track.Genre
@release.Genre
</MudChip>
</div>
}
@if (track.ReleaseDate is not null)
@if (release?.ReleaseDate is not null)
{
<div>
<MudText Typo="Typo.overline">Released</MudText>
<MudText Typo="Typo.body1">@track.ReleaseDate.Value.ToString("MMMM yyyy")</MudText>
<MudText Typo="Typo.body1">@release.ReleaseDate.Value.ToString("MMMM yyyy")</MudText>
</div>
}
</div>
@@ -21,8 +21,8 @@ public interface ITrackDataService
string? album = null,
string? genre = null);
/// <summary>Distinct non-null albums with track counts and a representative cover key.</summary>
Task<ApiResult<List<AlbumSummaryDto>>> GetAlbums();
/// <summary>All releases with track counts, title-ascending.</summary>
Task<ApiResult<List<ReleaseDto>>> GetAlbums();
/// <summary>Distinct non-null genres with track counts.</summary>
Task<ApiResult<List<GenreSummaryDto>>> GetGenres();
@@ -29,7 +29,7 @@ public class TrackClientDataService : ITrackDataService
string? genre = null)
=> _trackClient.GetPage(pageNumber, pageSize, sortColumn, sortDescending, searchText, album, genre);
public Task<ApiResult<List<AlbumSummaryDto>>> GetAlbums()
public Task<ApiResult<List<ReleaseDto>>> GetAlbums()
=> _trackClient.GetAlbums();
public Task<ApiResult<List<GenreSummaryDto>>> GetGenres()