feat(cms): dedicated grid columns for medium-specific row actions

Replace CmsAlbumBrowser's single RowActions slot with a SpecialColumns
list (header + per-row cell). Mix renders a Waveform column, Session a
Hero column, between Tracks and Actions; Edit/Delete stay rightmost.
Child-row colspan now computed from column count. Cut/ALL unchanged.
This commit is contained in:
daniel-c-harvey
2026-06-15 11:16:15 -04:00
parent 4df2b8fb57
commit 5941f1f23a
4 changed files with 126 additions and 81 deletions
@@ -33,6 +33,10 @@ else
<MudTh>Release Date</MudTh>
<MudTh>Type</MudTh>
<MudTh Style="width: 1%; white-space: nowrap;">Tracks</MudTh>
@foreach (var column in SpecialColumns)
{
<MudTh Style="width: 1%; white-space: nowrap;">@column.Header</MudTh>
}
<MudTh Style="width: 1%; white-space: nowrap;">Actions</MudTh>
</HeaderContent>
<RowTemplate>
@@ -64,11 +68,14 @@ else
</MudChip>
</MudTd>
<MudTd DataLabel="Tracks">@context.TrackCount</MudTd>
@foreach (var column in SpecialColumns)
{
@* One dedicated cell per host-declared special-action column (Mix waveform, Session hero).
The Cell fragment recovers its typed row state via the host's RowFor lookup. Sits between
Tracks and Actions so the universal Edit/Delete stay rightmost. *@
<MudTd DataLabel="@column.Header">@column.Cell(context.Release)</MudTd>
}
<MudTd DataLabel="Actions">
@* Medium-specific row action (Session hero, Mix waveform) when a host supplies one;
the ALL tab supplies none. Rendered before the shared edit/delete so the medium
affordance reads left-to-right ahead of the universal actions. *@
@RowActions?.Invoke(context.Release)
<MudTooltip Text="Batch Edit">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Size="Size.Small"
@@ -88,7 +95,7 @@ else
@if (context.IsExpanded)
{
<MudTr>
<MudTd colspan="9" Style="padding: 0;">
<MudTd colspan="@ColumnCount" Style="padding: 0;">
@if (context.IsLoading)
{
<MudStack Row="true" AlignItems="AlignItems.Center" Class="pa-2">
@@ -126,12 +133,19 @@ else
[Parameter] public bool IsLoading { get; set; }
[Parameter] public EventCallback OnReleasesChanged { get; set; }
// Optional per-row, medium-specific action slot (Session hero upload, Mix waveform generate),
// rendered in the Actions cell ahead of the shared edit/delete buttons. The ALL tab leaves it
// unset and renders the grid exactly as before. A per-medium host (CmsCut/Session/MixBrowser)
// supplies it so the rich grid filtered to one medium keeps that medium's bespoke affordance
// the rich expand/delete/Type-chip/edit logic stays here, single-sourced, rather than forked.
[Parameter] public RenderFragment<ReleaseDto>? RowActions { get; set; }
// Zero or more dedicated, header-labelled special-action columns (Session hero upload, Mix waveform
// generate), each rendered as its own header cell + per-row cell between the Tracks and Actions
// columns. The ALL and Cut tabs leave this empty and render exactly as before — only the standard
// columns plus Edit/Delete. A per-medium host supplies its bespoke affordances here so the rich
// expand/delete/Type-chip/edit logic stays single-sourced in this grid rather than forked.
[Parameter] public IReadOnlyList<SpecialActionColumn> SpecialColumns { get; set; } = Array.Empty<SpecialActionColumn>();
// Base columns: expand, Art, Album, Artist, Genre, Release Date, Type, Tracks, Actions = 9.
private const int BaseColumnCount = 9;
// Total rendered columns, driving the expanded child-row colspan so it always spans the full table
// regardless of how many special-action columns the host declared.
private int ColumnCount => BaseColumnCount + SpecialColumns.Count;
private List<AlbumRow> _rows = new();