31084b09a4
Allocate _specialColumns once in OnInitialized; update RowActions references to SpecialColumns in the medium browsers and base class.
96 lines
4.6 KiB
C#
96 lines
4.6 KiB
C#
using DeepDrftManager.Services;
|
|
using DeepDrftModels.DTOs;
|
|
using DeepDrftModels.Enums;
|
|
using Microsoft.AspNetCore.Components;
|
|
using MudBlazor;
|
|
|
|
namespace DeepDrftManager.Components.Pages.Tracks;
|
|
|
|
/// <summary>
|
|
/// Shared fetch + state logic for the per-medium browsers (Cuts, Sessions, Mixes). Each subclass feeds
|
|
/// the rich <c>CmsAlbumBrowser</c> grid a medium-filtered release list, so the per-medium tabs gain the
|
|
/// same expand-tracks / delete / Type-chip / edit behaviour as the ALL tab without re-implementing any of
|
|
/// it (§8.C parity — reuse, don't fork). This base owns the loading flag, the medium-filtered load, the
|
|
/// per-release row projection, and a cover-thumbnail helper; subclasses supply the <see cref="Medium"/>,
|
|
/// an error noun, and their bespoke per-row action (Session hero upload, Mix waveform generate) via the
|
|
/// rich grid's <c>SpecialColumns</c> column model, looking their action-state row up with <see cref="RowFor"/>.
|
|
/// </summary>
|
|
/// <typeparam name="TRow">The subclass's row model wrapping a <see cref="ReleaseDto"/> plus its
|
|
/// medium-specific action state (upload/generate flags). The rich grid renders from the bare
|
|
/// <see cref="Releases"/> projection; <typeparamref name="TRow"/> only carries the action state.</typeparam>
|
|
public abstract class CmsMediumBrowserBase<TRow> : ComponentBase where TRow : class
|
|
{
|
|
[Inject] public required ICmsReleaseService CmsReleaseService { get; set; }
|
|
[Inject] public required ISnackbar Snackbar { get; set; }
|
|
|
|
/// <summary>The medium this browser lists. Subclass-supplied constant.</summary>
|
|
protected abstract ReleaseMedium Medium { get; }
|
|
|
|
/// <summary>Plural noun for this medium used in error text (e.g. "sessions", "mixes").</summary>
|
|
protected abstract string MediumNoun { get; }
|
|
|
|
/// <summary>Projects a fetched release into the subclass's row model.</summary>
|
|
protected abstract TRow ToRow(ReleaseDto release);
|
|
|
|
/// <summary>The release carried by a subclass row, for keying the action-state lookup.</summary>
|
|
protected abstract ReleaseDto ReleaseOf(TRow row);
|
|
|
|
protected List<TRow> Rows { get; private set; } = new();
|
|
protected bool Loading { get; private set; } = true;
|
|
|
|
// Bare release projection handed to the rich grid. The grid does the expand/delete/edit/Type-chip;
|
|
// it never sees TRow. Rebuilt on every (re)load so the grid re-projects against a fresh reference.
|
|
protected IReadOnlyList<ReleaseDto> Releases { get; private set; } = Array.Empty<ReleaseDto>();
|
|
|
|
// release.Id → action-state row, so a SpecialColumns cell delegate (which the grid hands a ReleaseDto)
|
|
// can recover its TRow. Rebuilt alongside Rows so a refresh never leaves a stale row behind.
|
|
private Dictionary<long, TRow> _rowsById = new();
|
|
|
|
protected override async Task OnInitializedAsync() => await LoadAsync();
|
|
|
|
/// <summary>Recovers the action-state row for a release the rich grid is rendering. Null if the
|
|
/// release is not in the current page (e.g. just deleted), in which case the action is skipped.</summary>
|
|
protected TRow? RowFor(ReleaseDto release) =>
|
|
_rowsById.TryGetValue(release.Id, out var row) ? row : null;
|
|
|
|
/// <summary>
|
|
/// Reloads the medium-filtered release list. Wired to the rich grid's <c>OnReleasesChanged</c> so a
|
|
/// delete re-fetches the authoritative list (track counts, orphan cleanup) — the same single-load
|
|
/// posture <c>CmsAllReleasesGrid</c> uses for the ALL tab.
|
|
/// </summary>
|
|
protected async Task ReloadAsync()
|
|
{
|
|
await LoadAsync();
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
Loading = true;
|
|
// Single-track releases; a single generous page covers the CMS catalogue (same small-catalogue
|
|
// assumption the album browser makes).
|
|
var result = await CmsReleaseService.GetPagedAsync(
|
|
Medium, page: 1, pageSize: 100,
|
|
sortColumn: "Title", sortDescending: false);
|
|
|
|
if (result.Success && result.Value is not null)
|
|
{
|
|
Rows = result.Value.Items.Select(ToRow).ToList();
|
|
}
|
|
else
|
|
{
|
|
var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
|
Snackbar.Add($"Failed to load {MediumNoun}: {error}", Severity.Error);
|
|
Rows = new List<TRow>();
|
|
}
|
|
|
|
Releases = Rows.Select(ReleaseOf).ToList();
|
|
_rowsById = Rows.ToDictionary(r => ReleaseOf(r).Id);
|
|
Loading = false;
|
|
}
|
|
|
|
// Relative path — resolves against the Manager's own origin, proxied by ImageProxyController.
|
|
protected static string ThumbUrl(string entryKey) =>
|
|
$"/api/image/{Uri.EscapeDataString(entryKey)}";
|
|
}
|