f02974b3c2
- Add CmsTrackBrowserViewModel.Invalidate(); called from TrackEdit/BatchEdit on save or delete so album/genre cache is invalidated and re-fetches on next mode switch
- CmsAlbumBrowser now handles 0-track releases: confirm dialog + DeleteReleaseAsync instead of early return; partial-failure path also fires OnReleasesChanged to trigger cache invalidation
- TrackList.OnAlbumsChanged now calls VM.Invalidate() so genres stay fresh after any album delete
- UnifiedTrackService.DeleteAsync cascades release soft-delete when last live track is removed (non-fatal; logs on failure)
- New DELETE api/track/release/{id} endpoint (ApiKeyAuthorize) for direct release soft-delete
- EF migration SoftDeleteOrphanedReleases backfills existing orphaned release rows via raw SQL (data-only, no schema change)
85 lines
3.0 KiB
C#
85 lines
3.0 KiB
C#
using DeepDrftModels.DTOs;
|
|
|
|
namespace DeepDrftManager.Services;
|
|
|
|
/// <summary>The three browse dimensions for the /tracks page.</summary>
|
|
public enum BrowseMode
|
|
{
|
|
Tracks,
|
|
Albums,
|
|
Genres,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Holds the /tracks browser's current mode plus the album- and genre-mode datasets. Scoped per
|
|
/// circuit. Album and genre lists are fetched lazily on first switch into their mode and cached for
|
|
/// the circuit's lifetime; Track mode owns its own paging inside <c>CmsTrackGrid</c> and needs no
|
|
/// state here.
|
|
/// </summary>
|
|
public class CmsTrackBrowserViewModel
|
|
{
|
|
private readonly ICmsTrackService _trackService;
|
|
|
|
public CmsTrackBrowserViewModel(ICmsTrackService trackService)
|
|
{
|
|
_trackService = trackService;
|
|
}
|
|
|
|
public BrowseMode Mode { get; private set; } = BrowseMode.Tracks;
|
|
|
|
// Album mode.
|
|
public IReadOnlyList<ReleaseDto> Albums { get; private set; } = Array.Empty<ReleaseDto>();
|
|
public bool AlbumsLoading { get; private set; }
|
|
|
|
// Genre mode.
|
|
public IReadOnlyList<GenreSummaryDto> Genres { get; private set; } = Array.Empty<GenreSummaryDto>();
|
|
public bool GenresLoading { get; private set; }
|
|
public string? ExpandedGenre { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Switch the active mode, lazily loading the album or genre dataset on first entry. Collapses
|
|
/// any expanded genre row. The grid in Track mode owns its own data, so no fetch happens there.
|
|
/// </summary>
|
|
public async Task SwitchModeAsync(BrowseMode mode)
|
|
{
|
|
Mode = mode;
|
|
ExpandedGenre = null; // collapse on mode switch
|
|
|
|
if (mode == BrowseMode.Albums && Albums.Count == 0 && !AlbumsLoading)
|
|
{
|
|
AlbumsLoading = true;
|
|
var result = await _trackService.GetReleasesAsync();
|
|
Albums = result.Success && result.Value is not null
|
|
? result.Value
|
|
: Array.Empty<ReleaseDto>();
|
|
AlbumsLoading = false;
|
|
}
|
|
else if (mode == BrowseMode.Genres && Genres.Count == 0 && !GenresLoading)
|
|
{
|
|
GenresLoading = true;
|
|
var result = await _trackService.GetGenreSummariesAsync();
|
|
Genres = result.Success && result.Value is not null
|
|
? result.Value
|
|
: Array.Empty<GenreSummaryDto>();
|
|
GenresLoading = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>Toggle the expanded genre row. Selecting the already-expanded genre collapses it.</summary>
|
|
public void SetExpandedGenre(string? genre)
|
|
{
|
|
ExpandedGenre = ExpandedGenre == genre ? null : genre;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Drop the cached album and genre datasets so the next <see cref="SwitchModeAsync"/> into
|
|
/// either mode re-fetches from the API. Call after a track or release mutation (edit, delete)
|
|
/// since both datasets are derived from the catalogue and go stale on any such change.
|
|
/// </summary>
|
|
public void Invalidate()
|
|
{
|
|
Albums = Array.Empty<ReleaseDto>();
|
|
Genres = Array.Empty<GenreSummaryDto>();
|
|
}
|
|
}
|