refactor(public): retire track-cardinal stack, fold Archive/Cuts cards into ReleaseGallery (P11 W3 §4)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* Single space-between row under the waveform: identity on the left, accents on the right.
|
||||
Colours come from the MudBlazor theme (the dock surface is theme-aware), so unlike the
|
||||
always-dark TrackCard glass we do not hard-code green-accent overrides here. */
|
||||
Colours come from the MudBlazor theme (the dock surface is theme-aware), so we do not
|
||||
hard-code green-accent overrides here. */
|
||||
.track-meta-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</p>
|
||||
<div class="hero-actions @AnimClass">
|
||||
<StreamNowButton ButtonClass="btn-primary" ButtonLabel="Start Streaming" />
|
||||
<a class="btn-ghost" href="/tracks">Browse Tracks</a>
|
||||
<a class="btn-ghost" href="/archive">Browse Tracks</a>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace DeepDrftPublic.Client.Controls;
|
||||
|
||||
public enum GalleryViewMode
|
||||
{
|
||||
Grid,
|
||||
List
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public partial class PlayStateIcon : ComponentBase, IDisposable
|
||||
{
|
||||
// The cascade is IsFixed, so the provider's re-render never reaches us; subscribe to the
|
||||
// multicast side-channel to re-render on every player state change. Reference-guarded so
|
||||
// re-parametering is idempotent. Mirrors AudioPlayerBar / TracksView.
|
||||
// re-parametering is idempotent. Mirrors AudioPlayerBar.
|
||||
if (PlayerService != null && !ReferenceEquals(PlayerService, _subscribedService))
|
||||
{
|
||||
if (_subscribedService != null)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@* Invariant trio shared by every medium's detail page: a back link, a masthead (title + artist),
|
||||
a play/share affordance row wired to the streaming player, and slots for the medium-specific
|
||||
hero visual and metadata block. TrackDetail and the Session/Mix detail pages all compose this;
|
||||
hero visual and metadata block. The Cut/Session/Mix detail pages all compose this;
|
||||
per-medium variance rides the Hero and MetaContent render fragments. *@
|
||||
|
||||
<div class="deepdrft-track-detail-container">
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace DeepDrftPublic.Client.Controls;
|
||||
/// <summary>
|
||||
/// Shared detail-page chrome for any release medium: back link, masthead, play/share affordance,
|
||||
/// and hero/meta slots. Owns the play-toggle wiring against the cascaded streaming player so each
|
||||
/// detail page supplies only its data and medium-specific visuals. Extracted from the original
|
||||
/// TrackDetail page, which is now a thin consumer of this scaffold.
|
||||
/// detail page supplies only its data and medium-specific visuals. Each medium's detail page is a
|
||||
/// thin consumer of this scaffold.
|
||||
/// </summary>
|
||||
public partial class ReleaseDetailScaffold : ComponentBase
|
||||
{
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
@namespace DeepDrftPublic.Client.Controls
|
||||
|
||||
@* Card grid of releases that open their own detail page (/{DetailRoute}/{id}). Shared by the
|
||||
Sessions and Mixes browse pages. Cuts intentionally do not use this — they open the track
|
||||
gallery filtered by album, a different navigation target. Fully controlled by the parent:
|
||||
loading and item state are passed in. *@
|
||||
@* The single release-card grid for every browse surface (Sessions, Mixes, Cuts, Archive). Cards
|
||||
open a detail page; how a card computes its href is the only real divergence across surfaces, so
|
||||
the parent supplies it one of two ways:
|
||||
- DetailRoute (the simple default): every card links /{DetailRoute}/{id} (Sessions, Mixes).
|
||||
- HrefResolver (per-card): each card links HrefResolver(release), so Archive routes each card by
|
||||
its own medium through the one ReleaseRoutes table, and Cuts routes to /cuts/{id}.
|
||||
HrefResolver wins when both are supplied. The card subtitle defaults to the artist; SubtitleResolver
|
||||
overrides it (Cuts shows a track count instead). Fully controlled by the parent: loading and item
|
||||
state are passed in. *@
|
||||
|
||||
<div>
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="release-gallery-container">
|
||||
@@ -33,7 +38,7 @@
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4" lg="3" xl="3">
|
||||
<div class="release-card-center">
|
||||
<a href="@($"/{DetailRoute}/{release.Id}")" class="release-card-link">
|
||||
<a href="@CardHref(release)" class="release-card-link">
|
||||
<div class="release-card">
|
||||
@if (!string.IsNullOrEmpty(release.ImagePath))
|
||||
{
|
||||
@@ -51,7 +56,7 @@
|
||||
@release.Title
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="release-card-artist text-truncate">
|
||||
@release.Artist
|
||||
@(SubtitleResolver?.Invoke(release) ?? release.Artist)
|
||||
</MudText>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,8 +73,27 @@
|
||||
[Parameter] public required IReadOnlyList<DeepDrftModels.DTOs.ReleaseDto> Releases { get; set; }
|
||||
[Parameter] public bool Loading { get; set; }
|
||||
|
||||
/// <summary>Route segment for a card's detail page; a card links to /{DetailRoute}/{id}.</summary>
|
||||
[Parameter] public required string DetailRoute { get; set; }
|
||||
/// <summary>
|
||||
/// Route segment for a card's detail page; a card links to /{DetailRoute}/{id}. The simple
|
||||
/// fixed-route default used by Sessions/Mixes. Ignored when <see cref="HrefResolver"/> is supplied.
|
||||
/// </summary>
|
||||
[Parameter] public string? DetailRoute { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Per-card href resolver. When supplied, a card links to its result instead of the
|
||||
/// <see cref="DetailRoute"/>-based href, letting Archive route each card by its own medium and
|
||||
/// Cuts route to /cuts/{id} (both via <c>ReleaseRoutes.DetailHref</c>).
|
||||
/// </summary>
|
||||
[Parameter] public Func<DeepDrftModels.DTOs.ReleaseDto, string>? HrefResolver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional override for a card's subtitle line (defaults to the release artist). Cuts pass a
|
||||
/// track-count label here.
|
||||
/// </summary>
|
||||
[Parameter] public Func<DeepDrftModels.DTOs.ReleaseDto, string>? SubtitleResolver { get; set; }
|
||||
|
||||
[Parameter] public string EmptyMessage { get; set; } = "Nothing here yet";
|
||||
|
||||
private string CardHref(DeepDrftModels.DTOs.ReleaseDto release)
|
||||
=> HrefResolver?.Invoke(release) ?? $"/{DetailRoute}/{release.Id}";
|
||||
}
|
||||
|
||||
@@ -1,213 +0,0 @@
|
||||
@{
|
||||
var hasLink = !string.IsNullOrEmpty(TrackModel?.EntryKey);
|
||||
var trackHref = hasLink ? $"/track/{TrackModel!.EntryKey}" : null;
|
||||
var hasArt = !string.IsNullOrEmpty(TrackModel?.Release?.ImagePath);
|
||||
}
|
||||
|
||||
@if (ViewMode == GalleryViewMode.Grid)
|
||||
{
|
||||
<div class="deepdrft-track-card-container @(hasArt ? "deepdrft-track-card-container--art" : "")">
|
||||
|
||||
@* Cover and title/artist link to the detail page; the play button (below, outside any
|
||||
anchor) stays the sole playback entry point. display:contents keeps the grid intact. *@
|
||||
@if (hasLink)
|
||||
{
|
||||
<a href="@trackHref" class="deepdrft-track-card-link">
|
||||
@if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
|
||||
{
|
||||
<div class="deepdrft-track-card-bg" style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="deepdrft-track-card-fallback"></div>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
|
||||
{
|
||||
<div class="deepdrft-track-card-bg" style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="deepdrft-track-card-fallback"></div>
|
||||
}
|
||||
|
||||
<div class="deepdrft-track-card-content">
|
||||
|
||||
@if (hasLink)
|
||||
{
|
||||
<a href="@trackHref" class="deepdrft-track-card-link">
|
||||
<div class="deepdrft-track-info-top">
|
||||
<MudText Typo="Typo.subtitle1"
|
||||
Class="deepdrft-track-title text-truncate mb-1">
|
||||
@TrackModel?.TrackName
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.caption"
|
||||
Class="deepdrft-track-artist text-truncate mb-2">
|
||||
@TrackModel?.Release?.Artist
|
||||
</MudText>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="deepdrft-track-info-top">
|
||||
<MudText Typo="Typo.subtitle1"
|
||||
Class="deepdrft-track-title text-truncate mb-1">
|
||||
@TrackModel?.TrackName
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.caption"
|
||||
Class="deepdrft-track-artist text-truncate mb-2">
|
||||
@TrackModel?.Release?.Artist
|
||||
</MudText>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="deepdrft-track-info-middle">
|
||||
@if (!string.IsNullOrEmpty(TrackModel?.Release?.Title))
|
||||
{
|
||||
<MudText Typo="Typo.caption"
|
||||
Class="deepdrft-track-meta text-truncate">
|
||||
@TrackModel.Release!.Title
|
||||
</MudText>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(TrackModel?.Release?.Genre))
|
||||
{
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Tertiary"
|
||||
Class="deepdrft-genre-chip">
|
||||
@TrackModel.Release!.Genre
|
||||
</MudChip>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="deepdrft-track-info-bottom">
|
||||
@if (TrackModel?.Release?.ReleaseDate.HasValue == true)
|
||||
{
|
||||
<MudText Typo="Typo.caption"
|
||||
Class="deepdrft-track-meta">
|
||||
@TrackModel.Release!.ReleaseDate!.Value.Year
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div></div>
|
||||
}
|
||||
|
||||
<MudFab Color="Color.Tertiary"
|
||||
Size="Size.Medium"
|
||||
StartIcon="@PlayPauseIcon"
|
||||
Disabled="@(!RendererInfo.IsInteractive)"
|
||||
OnClick="@PlayClick"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="deepdrft-track-row @(IsPlaying ? "deepdrft-track-row--playing" : "")">
|
||||
|
||||
<MudFab Color="Color.Tertiary"
|
||||
Size="Size.Medium"
|
||||
StartIcon="@PlayPauseIcon"
|
||||
Disabled="@(!RendererInfo.IsInteractive)"
|
||||
OnClick="@PlayClick"
|
||||
Class="deepdrft-track-row-fab"/>
|
||||
|
||||
@if (hasLink)
|
||||
{
|
||||
<a href="@trackHref" class="deepdrft-track-row-link">
|
||||
@* art thumb *@
|
||||
@if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
|
||||
{
|
||||
<div class="deepdrft-track-row-thumb"
|
||||
style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="deepdrft-track-row-thumb deepdrft-track-row-thumb--fallback"></div>
|
||||
}
|
||||
|
||||
@* text block *@
|
||||
<div class="deepdrft-track-row-text">
|
||||
<MudText Typo="Typo.subtitle2" Class="deepdrft-track-title text-truncate">
|
||||
@TrackModel?.Release?.Artist
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="deepdrft-track-meta text-truncate">
|
||||
@TrackModel?.TrackName
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
@* right metadata *@
|
||||
<div class="deepdrft-track-row-meta">
|
||||
@if (!string.IsNullOrEmpty(TrackModel?.Release?.Genre))
|
||||
{
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Tertiary"
|
||||
Class="deepdrft-genre-chip">
|
||||
@TrackModel.Release!.Genre
|
||||
</MudChip>
|
||||
}
|
||||
@if (TrackModel?.Release?.ReleaseDate.HasValue == true)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="deepdrft-track-meta">
|
||||
@TrackModel.Release!.ReleaseDate!.Value.Year
|
||||
</MudText>
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* same structure without anchor *@
|
||||
@if (!string.IsNullOrEmpty(TrackModel?.Release?.ImagePath))
|
||||
{
|
||||
<div class="deepdrft-track-row-thumb"
|
||||
style="background-image: url('api/image/@Uri.EscapeDataString(TrackModel.Release!.ImagePath)');">
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="deepdrft-track-row-thumb deepdrft-track-row-thumb--fallback"></div>
|
||||
}
|
||||
<div class="deepdrft-track-row-text">
|
||||
<MudText Typo="Typo.subtitle2" Class="deepdrft-track-title text-truncate">
|
||||
@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?.Release?.Genre))
|
||||
{
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Tertiary"
|
||||
Class="deepdrft-genre-chip">
|
||||
@TrackModel.Release!.Genre
|
||||
</MudChip>
|
||||
}
|
||||
@if (TrackModel?.Release?.ReleaseDate.HasValue == true)
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="deepdrft-track-meta">
|
||||
@TrackModel.Release!.ReleaseDate!.Value.Year
|
||||
</MudText>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using DeepDrftModels.DTOs;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using MudBlazor;
|
||||
|
||||
namespace DeepDrftPublic.Client.Controls;
|
||||
|
||||
public partial class TrackCard : ComponentBase
|
||||
{
|
||||
[Parameter] public required TrackDto TrackModel { get; set; }
|
||||
[Parameter] public EventCallback<TrackDto> OnPlay { get; set; }
|
||||
[Parameter] public EventCallback<TrackDto> OnPause { get; set; }
|
||||
[Parameter] public bool IsPlaying { get; set; } = false;
|
||||
[Parameter] public bool IsPaused { get; set; } = false;
|
||||
[Parameter] public GalleryViewMode ViewMode { get; set; } = GalleryViewMode.Grid;
|
||||
|
||||
// Pause only when actively playing; every other state (idle, paused) reads as "press to play".
|
||||
private bool IsActivelyPlaying => IsPlaying && !IsPaused;
|
||||
|
||||
private string PlayPauseIcon =>
|
||||
IsActivelyPlaying ? Icons.Material.Filled.Pause : Icons.Material.Filled.PlayArrow;
|
||||
|
||||
private async Task PlayClick()
|
||||
{
|
||||
if (IsActivelyPlaying)
|
||||
{
|
||||
if (OnPause.HasDelegate)
|
||||
await OnPause.InvokeAsync(TrackModel);
|
||||
}
|
||||
else if (OnPlay.HasDelegate)
|
||||
{
|
||||
await OnPlay.InvokeAsync(TrackModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/* Container — transparent so the absolute-positioned fallback panel or album art
|
||||
controls the card's background. Glass edge matches NowPlayingCard vocabulary. */
|
||||
.deepdrft-track-card-container {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
min-width: 250px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border: 2px solid var(--mud-palette-secondary);
|
||||
}
|
||||
|
||||
.deepdrft-track-card-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
filter: brightness(0.7);
|
||||
}
|
||||
|
||||
.deepdrft-track-card-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
background: linear-gradient(to top,
|
||||
rgba(13, 27, 42, 0.75) 0%,
|
||||
rgba(13, 27, 42, 0.35) 45%,
|
||||
rgba(13, 27, 42, 0.00) 100%);
|
||||
}
|
||||
|
||||
/* Fallback panel — solid navy, opaque so the card reads correctly on both
|
||||
light and dark page backgrounds. Semi-transparent + blur washes out on white. */
|
||||
.deepdrft-track-card-fallback {
|
||||
position: absolute;
|
||||
top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: var(--deepdrft-navy-mid, #162437);
|
||||
border: 1px solid rgba(250, 250, 248, 0.12);
|
||||
}
|
||||
|
||||
/* Title: off-white — matches .np-title.
|
||||
::deep required: MudText renders its own element, so Blazor isolation
|
||||
won't stamp b-{hash} on it; ::deep pierces into child component output. */
|
||||
::deep .deepdrft-track-title { color: var(--deepdrft-white, #FAFAF8); }
|
||||
|
||||
/* Artist: muted off-white — green reserved for interactive elements (FAB, chip). ::deep for same reason. */
|
||||
::deep .deepdrft-track-artist { color: rgba(250, 250, 248, 0.55); }
|
||||
|
||||
/* Meta: muted off-white — matches .np-sub. ::deep for same reason. */
|
||||
::deep .deepdrft-track-meta { color: rgba(250, 250, 248, 0.45); }
|
||||
|
||||
/* FAB always green-interactive — card is always dark glass regardless of page theme.
|
||||
.mud-button-filled-tertiary specificity (0,1,0) in MudBlazor; our (0,1,1) wins. */
|
||||
::deep .mud-button-filled-tertiary {
|
||||
background-color: var(--deepdrft-green-interactive, #3aa163);
|
||||
color: var(--deepdrft-white, #FAFAF8);
|
||||
}
|
||||
|
||||
/* Genre chip always green-accent outline/text on the dark glass card. */
|
||||
::deep .deepdrft-genre-chip.mud-chip-outlined {
|
||||
border-color: var(--deepdrft-green-accent, #3D7A68);
|
||||
color: var(--deepdrft-green-accent, #3D7A68);
|
||||
}
|
||||
::deep .deepdrft-genre-chip.mud-chip-color-tertiary {
|
||||
color: var(--deepdrft-green-accent, #3D7A68);
|
||||
}
|
||||
|
||||
.deepdrft-track-info-middle { margin: 8px 0; }
|
||||
|
||||
.deepdrft-track-info-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.deepdrft-track-card-container {
|
||||
min-width: 200px;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mode A: hover-reveal overlay (art cards only) ──────────────────────── */
|
||||
|
||||
/* Gate the hidden-at-rest rule on (a) art present and (b) a hover-capable pointer.
|
||||
Fallback cards (no --art modifier) and touch devices always show the overlay. */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.deepdrft-track-card-container--art .deepdrft-track-card-content {
|
||||
opacity: 0;
|
||||
background: transparent;
|
||||
transition: opacity 180ms ease, background-color 180ms ease;
|
||||
}
|
||||
.deepdrft-track-card-container--art:hover .deepdrft-track-card-content {
|
||||
opacity: 1;
|
||||
background: rgba(22, 36, 55, 0.82);
|
||||
transition: opacity 180ms ease, background-color 180ms ease;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mode B: list row ───────────────────────────────────────────────────── */
|
||||
|
||||
.deepdrft-track-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
height: 80px;
|
||||
padding: 8px 16px;
|
||||
background: var(--mud-palette-surface);
|
||||
border: 1px solid var(--mud-palette-divider);
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.deepdrft-track-row-link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
::deep .deepdrft-track-row-fab {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.deepdrft-track-row-thumb {
|
||||
flex: 0 0 64px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.deepdrft-track-row-thumb--fallback {
|
||||
background: var(--deepdrft-navy-mid);
|
||||
border: 1px solid var(--mud-palette-divider);
|
||||
}
|
||||
|
||||
.deepdrft-track-row-text {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.deepdrft-track-row-meta {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.deepdrft-track-row {
|
||||
height: auto;
|
||||
min-height: 72px;
|
||||
padding: 8px 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
.deepdrft-track-row-thumb {
|
||||
flex: 0 0 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.deepdrft-track-row--playing {
|
||||
border-left: 3px solid var(--deepdrft-green-interactive, #3aa163);
|
||||
}
|
||||
|
||||
/* ── Mode B text: theme-aware overrides (navy on light / off-white on dark) ─ */
|
||||
|
||||
/* The global ::deep rules above hard-code off-white for the dark glass grid cards.
|
||||
List rows use --mud-palette-surface as their background, so text must follow
|
||||
the theme. These selectors have higher specificity (.deepdrft-track-row[b-hash]
|
||||
vs plain [b-hash]) and win in the cascade. */
|
||||
.deepdrft-track-row ::deep .deepdrft-track-title,
|
||||
.deepdrft-track-row ::deep .deepdrft-track-artist,
|
||||
.deepdrft-track-row ::deep .deepdrft-track-meta {
|
||||
color: var(--mud-palette-text-primary);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
@if (ViewMode == GalleryViewMode.Grid)
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="tracks-gallery-container">
|
||||
<MudGrid Spacing="6" Justify="Justify.Center">
|
||||
@foreach (var track in Tracks)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4" lg="3" xl="3">
|
||||
<div class="deepdrft-track-gallery-item-center">
|
||||
<TrackCard TrackModel="@track"
|
||||
ViewMode="@ViewMode"
|
||||
IsPlaying="@(IsPlaying && ActiveTrack?.Id == track.Id)"
|
||||
IsPaused="@(IsPaused && ActiveTrack?.Id == track.Id)"
|
||||
OnPlay="@HandlePlayClick"
|
||||
OnPause="@HandlePauseClick"/>
|
||||
</div>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudContainer MaxWidth="MaxWidth.Large">
|
||||
<div class="deepdrft-track-list">
|
||||
@foreach (var track in Tracks)
|
||||
{
|
||||
<TrackCard TrackModel="@track"
|
||||
ViewMode="@ViewMode"
|
||||
IsPlaying="@(IsPlaying && ActiveTrack?.Id == track.Id)"
|
||||
IsPaused="@(IsPaused && ActiveTrack?.Id == track.Id)"
|
||||
OnPlay="@HandlePlayClick"
|
||||
OnPause="@HandlePauseClick"/>
|
||||
}
|
||||
</div>
|
||||
</MudContainer>
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using DeepDrftModels.DTOs;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace DeepDrftPublic.Client.Controls;
|
||||
|
||||
public partial class TracksGallery : ComponentBase
|
||||
{
|
||||
[Parameter] public IEnumerable<TrackDto> Tracks { get; set; } = [];
|
||||
|
||||
// Controlled play-state inputs: the parent owns playback truth (the player service)
|
||||
// and drives these. The gallery is presentational — it only matches by id to decide
|
||||
// which card reflects the active state.
|
||||
[Parameter] public TrackDto? ActiveTrack { get; set; }
|
||||
[Parameter] public bool IsPlaying { get; set; }
|
||||
[Parameter] public bool IsPaused { get; set; }
|
||||
[Parameter] public GalleryViewMode ViewMode { get; set; } = GalleryViewMode.Grid;
|
||||
|
||||
[Parameter] public EventCallback<TrackDto> OnPlay { get; set; }
|
||||
[Parameter] public EventCallback<TrackDto> OnPause { get; set; }
|
||||
|
||||
private Task HandlePlayClick(TrackDto track) =>
|
||||
OnPlay.HasDelegate ? OnPlay.InvokeAsync(track) : Task.CompletedTask;
|
||||
|
||||
private Task HandlePauseClick(TrackDto track) =>
|
||||
OnPause.HasDelegate ? OnPause.InvokeAsync(track) : Task.CompletedTask;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
.tracks-gallery-container {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.deepdrft-track-gallery-item-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.deepdrft-track-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
Reference in New Issue
Block a user