feat(cms): bring per-medium tab grids to ALL-tab parity (§8.C)

Render the rich CmsAlbumBrowser filtered per medium in the CUTS/SESSIONS/MIXES
tabs via an optional RowActions slot; retire the thin CmsMediumTable. Session
hero and Mix waveform actions preserved; ALL tab and TrackList unchanged.
This commit is contained in:
daniel-c-harvey
2026-06-13 22:33:31 -04:00
parent 4b9e6531fd
commit 3ef98aa3ff
8 changed files with 151 additions and 191 deletions
@@ -6,10 +6,11 @@
@inject ILogger<CmsMixBrowser> Logger
@* Embedded as the MIXES tab content of the Release Archive (Phase 9 §8.A), and still routable at
/tracks/mixes for direct-URL access. When embedded, the page chrome (title, container, the now-
meaningless "Back to Release Archive" button) is suppressed — the host tab strip owns that frame; only
the grid renders. The standalone route keeps the full page chrome. The per-row waveform affordance
(9.5.E) is preserved in both contexts. *@
/tracks/mixes for direct-URL access. The grid is the rich CmsAlbumBrowser filtered to Mixes (§8.C
parity: expand-tracks, delete, Type chip, per-row edit), with the Mix waveform generate supplied as
its medium-specific RowActions slot so that affordance survives the move off the thin table. When
embedded, the page chrome (title, container, the now-meaningless "Back to Release Archive" button) is
suppressed; the standalone route keeps it. The waveform affordance (9.5.E) is preserved in both. *@
@if (Embedded)
{
@GridContent
@@ -48,45 +49,49 @@ else
HasWaveform = !string.IsNullOrEmpty(release.MixMetadata?.WaveformEntryKey)
};
protected override ReleaseDto ReleaseOf(MixRow row) => row.Release;
// The grid itself — identical in the embedded and standalone contexts. Defined once as a fragment so
// both branches above render the same markup without duplication.
private RenderFragment GridContent => @<CmsMediumTable TRow="MixRow"
Rows="Rows"
Loading="Loading"
ReleaseAccessor="@(row => row.Release)"
ThumbUrl="@(key => ThumbUrl(key))"
TitleHeader="Mix"
EmptyMessage="No mixes found.">
<ActionContent Context="row">
@if (row.HasWaveform)
// both branches above render the same markup without duplication. The waveform generate is the Mix's
// medium-specific RowActions content; the grid hands it each release, and RowFor recovers the
// matching MixRow's generate state.
private RenderFragment GridContent => @<CmsAlbumBrowser Releases="Releases"
IsLoading="Loading"
OnReleasesChanged="ReloadAsync">
<RowActions Context="release">
@{ var row = RowFor(release); }
@if (row is not null)
{
<MudTooltip Text="Waveform generated">
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small" />
</MudTooltip>
}
else
{
<MudTooltip Text="No waveform — incomplete">
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Warning" Size="Size.Small" />
</MudTooltip>
}
<MudButton Variant="Variant.Outlined"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.GraphicEq"
Disabled="@row.IsGenerating"
OnClick="@(() => GenerateWaveformAsync(row))">
@if (row.IsGenerating)
@if (row.HasWaveform)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Generating…</span>
<MudTooltip Text="Waveform generated">
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small" />
</MudTooltip>
}
else
{
<span>@(row.HasWaveform ? "Regenerate" : "Generate")</span>
<MudTooltip Text="No waveform — incomplete">
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Warning" Size="Size.Small" />
</MudTooltip>
}
</MudButton>
</ActionContent>
</CmsMediumTable>;
<MudButton Variant="Variant.Outlined"
Size="Size.Small"
StartIcon="@Icons.Material.Filled.GraphicEq"
Disabled="@row.IsGenerating"
OnClick="@(() => GenerateWaveformAsync(row))">
@if (row.IsGenerating)
{
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
<span>Generating…</span>
}
else
{
<span>@(row.HasWaveform ? "Regenerate" : "Generate")</span>
}
</MudButton>
}
</RowActions>
</CmsAlbumBrowser>;
private async Task GenerateWaveformAsync(MixRow row)
{