@using System.Net @using DeepDrftManager.Services @using DeepDrftModels.DTOs @inject ICmsTrackService CmsTrackService @inject IDialogService DialogService @inject ISnackbar Snackbar @inject ILogger Logger @inject NavigationManager NavigationManager @if (ShowAddButton) { Add Track } No tracks found. Loading tracks… Track # Art Track Name Artist Album Genre Release Date Waveform Actions @context.TrackNumber @if (!string.IsNullOrEmpty(context.Release?.ImagePath)) {
} else {
}
@context.TrackName @(context.Release?.Artist ?? "—") @(context.Release?.Title ?? "—") @(context.Release?.Genre ?? "—") @(context.Release?.ReleaseDate?.ToString("d MMMM, yyyy") ?? "—") @if (HasProfile(context.EntryKey)) { } else { }
Entry: @context.EntryKey
File: @(context.OriginalFileName ?? "—")
@if (!HasProfile(context.EntryKey)) { }
@code { [Parameter] public string? AlbumFilter { get; set; } [Parameter] public string? GenreFilter { get; set; } [Parameter] public bool ShowAddButton { get; set; } = true; [Parameter] public int PageSize { get; set; } = 20; [Parameter] public EventCallback OnTracksChanged { get; set; } [Parameter] public EventCallback OnStatusLoaded { get; set; } private MudTable? _table; // EntryKey → HasProfile. Loaded once on init; per-row generate flips a single entry to true. private Dictionary _waveformStatus = new(); private readonly HashSet _generating = new(); // The parent owns "Generate All Missing"; while it runs it disables this grid's per-row buttons. private bool _bulkRunning; protected override async Task OnInitializedAsync() { await RefreshWaveformStatusAsync(); } private bool HasProfile(string entryKey) => _waveformStatus.TryGetValue(entryKey, out var hasProfile) && hasProfile; // Relative path — resolves against the Manager's own origin, proxied by ImageProxyController. private static string ThumbUrl(string imagePath) => $"/api/image/{Uri.EscapeDataString(imagePath)}"; /// Number of tracks with a missing waveform profile — drives the parent's bulk button label. public int GetMissingCount() => _waveformStatus.Count(kv => !kv.Value); /// /// Reload the full waveform-status map. Called on init and by the parent after a bulk generate so /// the per-row icons reflect the new state. /// public async Task RefreshWaveformStatusAsync() { var result = await CmsTrackService.GetWaveformStatusAsync(); _waveformStatus = result.Success && result.Value is not null ? result.Value.ToDictionary(s => s.EntryKey, s => s.HasProfile) : new Dictionary(); StateHasChanged(); await OnStatusLoaded.InvokeAsync(); } /// Set by the parent while its bulk generate runs so per-row buttons disable. public void SetBulkRunning(bool running) { _bulkRunning = running; StateHasChanged(); } private async Task> LoadServerData(TableState state, CancellationToken cancellationToken) { var pageNumber = state.Page + 1; // MudTable is 0-based, service is 1-based. var sortColumn = string.IsNullOrEmpty(state.SortLabel) ? "TrackName" : state.SortLabel; var sortDescending = state.SortDirection == SortDirection.Descending; var result = await CmsTrackService.GetPagedAsync( pageNumber, state.PageSize, sortColumn, sortDescending, AlbumFilter, GenreFilter, cancellationToken); if (!result.Success || result.Value is null) { var errorText = result.Messages.FirstOrDefault()?.Message ?? "Unknown error"; Snackbar.Add($"Failed to load tracks: {errorText}", Severity.Error); return new TableData { Items = Array.Empty(), TotalItems = 0 }; } var page = result.Value; return new TableData { Items = page.Items, TotalItems = page.TotalCount }; } private async Task ConfirmAndDelete(TrackDto track) { var confirmed = await DialogService.ShowMessageBox( title: "Delete track", markupMessage: new MarkupString($"Delete {WebUtility.HtmlEncode(track.TrackName)} by {WebUtility.HtmlEncode(track.Release?.Artist ?? "Unknown")}? This removes both the metadata row and the underlying audio entry."), yesText: "Delete", cancelText: "Cancel"); if (confirmed != true) return; try { var result = await CmsTrackService.DeleteTrackAsync(track.Id); if (result.Success) { Snackbar.Add($"Deleted '{track.TrackName}'.", Severity.Success); if (_table is not null) await _table.ReloadServerData(); await OnTracksChanged.InvokeAsync(); } else { var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error"; Snackbar.Add($"Delete failed: {error}", Severity.Error); } } catch (Exception ex) { Logger.LogError(ex, "Delete failed for track {TrackId}", track.Id); Snackbar.Add("Delete failed — please try again.", Severity.Error); } } private async Task GenerateOneAsync(TrackDto track) { _generating.Add(track.EntryKey); StateHasChanged(); try { var result = await CmsTrackService.GenerateWaveformProfileAsync(track.EntryKey); if (result.Success) { _waveformStatus[track.EntryKey] = true; Snackbar.Add($"Generated profile for '{track.TrackName}'.", Severity.Success); } else { var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error"; Snackbar.Add($"Generate failed for '{track.TrackName}': {error}", Severity.Error); } } catch (Exception ex) { Logger.LogError(ex, "Waveform generation failed for {EntryKey}", track.EntryKey); Snackbar.Add($"Generate failed for '{track.TrackName}' — please try again.", Severity.Error); } finally { _generating.Remove(track.EntryKey); StateHasChanged(); } } }