@page "/tracks/mixes" @inherits CmsMediumBrowserBase @using DeepDrftModels.DTOs @using DeepDrftModels.Enums @attribute [Authorize] @inject ILogger 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 { Mixes — DeepDrft CMS Back to Releases Mixes @GridContent } @code { /// /// 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. /// [Parameter] public bool Embedded { get; set; } /// /// Forwarded from the inner : fires after any per-row waveform /// generate succeeds so the parent page can refresh its catalogue-wide missing-count badges. /// [Parameter] public EventCallback OnWaveformGenerated { get; set; } private CmsAlbumBrowser? _albumBrowser; protected override ReleaseMedium Medium => ReleaseMedium.Mix; protected override string MediumNoun => "mixes"; /// /// Clears the inner grid's cached per-track waveform status so the next row expand re-fetches. /// Called by the parent page after a catalogue-wide bulk run. /// public Task InvalidateWaveformStatusAsync() => _albumBrowser?.InvalidateWaveformStatusAsync() ?? Task.CompletedTask; 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 => @; // Allocated once per component instance in OnInitialized (field initializers cannot reference // instance members, so initialization is deferred to the first lifecycle hook). private IReadOnlyList _specialColumns = Array.Empty(); 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 WaveformCell => release =>@ @{ var row = RowFor(release); } @if (row is not null) { @if (row.HasWaveform) { } else { } @if (row.IsGenerating) { Generating… } else { @(row.HasWaveform ? "Regenerate" : "Generate") } } ; 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; } } }