31084b09a4
Allocate _specialColumns once in OnInitialized; update RowActions references to SpecialColumns in the medium browsers and base class.
150 lines
6.2 KiB
Plaintext
150 lines
6.2 KiB
Plaintext
@page "/tracks/mixes"
|
|
@inherits CmsMediumBrowserBase<CmsMixBrowser.MixRow>
|
|
@using DeepDrftModels.DTOs
|
|
@using DeepDrftModels.Enums
|
|
@attribute [Authorize]
|
|
@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. 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 special-action column 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
|
|
}
|
|
else
|
|
{
|
|
<PageTitle>Mixes — DeepDrft CMS</PageTitle>
|
|
|
|
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
|
|
<MudButton Variant="Variant.Text"
|
|
StartIcon="@Icons.Material.Filled.ArrowBack"
|
|
Href="/tracks/archive"
|
|
Class="mb-4">
|
|
Back to Release Archive
|
|
</MudButton>
|
|
|
|
<MudText Typo="Typo.h4" GutterBottom="true">Mixes</MudText>
|
|
|
|
@GridContent
|
|
</MudContainer>
|
|
}
|
|
|
|
@code {
|
|
/// <summary>
|
|
/// True when rendered as tab content inside the Release Archive; suppresses the standalone page
|
|
/// chrome (title, container, back button). False (default) renders the full routable page.
|
|
/// </summary>
|
|
[Parameter] public bool Embedded { get; set; }
|
|
|
|
protected override ReleaseMedium Medium => ReleaseMedium.Mix;
|
|
protected override string MediumNoun => "mixes";
|
|
|
|
protected override MixRow ToRow(ReleaseDto release) => new()
|
|
{
|
|
Release = release,
|
|
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. The Mix declares one dedicated
|
|
// "Waveform" special-action column; the grid renders it between Tracks and Actions, handing the cell
|
|
// each release, and RowFor recovers the matching MixRow's generate state.
|
|
private RenderFragment GridContent => @<CmsAlbumBrowser Releases="Releases"
|
|
IsLoading="Loading"
|
|
OnReleasesChanged="ReloadAsync"
|
|
SpecialColumns="_specialColumns" />;
|
|
|
|
// Allocated once per component instance in OnInitialized (field initializers cannot reference
|
|
// instance members, so initialization is deferred to the first lifecycle hook).
|
|
private IReadOnlyList<SpecialActionColumn> _specialColumns = Array.Empty<SpecialActionColumn>();
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
_specialColumns = new[] { new SpecialActionColumn("Waveform", WaveformCell) };
|
|
base.OnInitialized();
|
|
}
|
|
|
|
// Per-row cell for the dedicated "Waveform" column: status icon plus generate/regenerate button with
|
|
// progress. Recovers the typed MixRow via RowFor; skips rendering for a release not on the page.
|
|
private RenderFragment<ReleaseDto> WaveformCell => release =>@<text>
|
|
@{ var row = RowFor(release); }
|
|
@if (row is not null)
|
|
{
|
|
@if (row.HasWaveform)
|
|
{
|
|
<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)
|
|
{
|
|
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
|
|
<span>Generating…</span>
|
|
}
|
|
else
|
|
{
|
|
<span>@(row.HasWaveform ? "Regenerate" : "Generate")</span>
|
|
}
|
|
</MudButton>
|
|
}
|
|
</text>;
|
|
|
|
private async Task GenerateWaveformAsync(MixRow row)
|
|
{
|
|
row.IsGenerating = true;
|
|
StateHasChanged();
|
|
try
|
|
{
|
|
var result = await CmsReleaseService.GenerateMixWaveformAsync(row.Release.Id);
|
|
if (result.Success)
|
|
{
|
|
// Optimistic update: the trigger succeeded, so the waveform is stored. Unlike SessionBrowser's
|
|
// re-fetch (which retrieves the server-generated HeroImageEntryKey), there is nothing to reflect
|
|
// back here — HasWaveform is derived from WaveformEntryKey being non-null, which we know is now set.
|
|
row.HasWaveform = true;
|
|
Snackbar.Add($"Generated waveform for '{row.Release.Title}'.", Severity.Success);
|
|
}
|
|
else
|
|
{
|
|
var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
|
Snackbar.Add($"Waveform generation failed for '{row.Release.Title}': {error}", Severity.Error);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogError(ex, "Waveform generation failed for release {ReleaseId}", row.Release.Id);
|
|
Snackbar.Add($"Waveform generation failed for '{row.Release.Title}' — please try again.", Severity.Error);
|
|
}
|
|
finally
|
|
{
|
|
row.IsGenerating = false;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
public sealed class MixRow
|
|
{
|
|
public required ReleaseDto Release { get; set; }
|
|
public bool HasWaveform { get; set; }
|
|
public bool IsGenerating { get; set; }
|
|
}
|
|
}
|