feat(cms): retire legacy single-track forms; route single-track edit into BatchEdit (8.M)

This commit is contained in:
daniel-c-harvey
2026-06-14 11:53:06 -04:00
parent 05130aaed2
commit 898fcfaa04
6 changed files with 67 additions and 603 deletions
@@ -1,4 +1,5 @@
@page "/tracks/album/{AlbumName}/edit"
@page "/tracks/{TrackId:long}/edit"
@using System.Security.Claims
@using DeepDrftManager.Services
@using DeepDrftModels.DTOs
@@ -111,8 +112,15 @@
// 1 GB ceiling matches DeepDrftAPI's per-request limit on api/track/upload.
private const long MaxUploadBytes = 1_073_741_824L;
// Release-title addressing (Album-mode batch Edit): loads the whole release by title.
[Parameter] public string AlbumName { get; set; } = string.Empty;
// Track-id addressing (Track-mode per-row Edit, §8.M): loads the addressed track's parent
// release and pre-selects that track's row, so editing a single Cut track lands the admin on
// the track they clicked rather than on the release with no row context. Null for the
// release-title route. The two routes are mutually exclusive — only one segment binds.
[Parameter] public long? TrackId { get; set; }
private List<BatchRowModel> _tracks = new();
private int _selectedIndex = -1;
private bool _loading = true;
@@ -154,12 +162,35 @@
protected override async Task OnInitializedAsync()
{
// Track-addressed entry (§8.M): resolve the addressed track to its parent release title,
// then fall through to the shared release-load path below. The clicked track's id is held
// for row pre-selection once the list is built.
var albumName = AlbumName;
if (TrackId is { } trackId)
{
var trackResult = await CmsTrackService.GetByIdAsync(trackId);
if (!trackResult.Success || trackResult.Value is not { } track)
{
_loadError = trackResult.Messages.FirstOrDefault()?.Message ?? "Track not found.";
_loading = false;
return;
}
albumName = track.Release?.Title;
if (string.IsNullOrEmpty(albumName))
{
_loadError = "This track has no parent release to edit.";
_loading = false;
return;
}
}
// A single page of 100 covers the full release (albums are small — same assumption as
// CmsAlbumBrowser). Sorted by track number so list order matches the saved ordinals.
var result = await CmsTrackService.GetPagedAsync(
page: 1, pageSize: 100,
sortColumn: "TrackNumber", sortDescending: false,
album: AlbumName);
album: albumName);
if (!result.Success || result.Value is null)
{
@@ -171,13 +202,13 @@
var tracks = result.Value.Items.ToList();
if (tracks.Count == 0)
{
_loadError = $"No tracks found for release '{AlbumName}'.";
_loadError = $"No tracks found for release '{albumName}'.";
_loading = false;
return;
}
var release = tracks[0].Release;
_albumName = AlbumName;
_albumName = albumName;
_artist = release?.Artist ?? string.Empty;
_genre = release?.Genre ?? string.Empty;
_releaseDate = release?.ReleaseDate?.ToString("yyyy-MM-dd") ?? string.Empty;
@@ -203,10 +234,24 @@
_tracks.RemoveRange(1, _tracks.Count - 1);
}
_selectedIndex = _tracks.Count > 0 ? 0 : -1;
// Track-addressed entry pre-selects the clicked row (§8.M Option 2). For a multi-track Cut
// the addressed track may be any ordinal; for single-track media it is always row 0 (the
// collapse above leaves one row). Fall back to row 0 if the id is absent or trimmed away.
_selectedIndex = ResolveInitialSelection();
_loading = false;
}
private int ResolveInitialSelection()
{
if (_tracks.Count == 0) return -1;
if (TrackId is { } trackId)
{
var addressed = _tracks.FindIndex(t => t.Id == trackId);
if (addressed >= 0) return addressed;
}
return 0;
}
private void HandleWavFilesSelected(IReadOnlyList<IBrowserFile> files)
{
_errorMessage = null;
@@ -471,7 +516,7 @@
if (!linkResult.Success)
{
// Non-blocking: track persisted; cover can be linked via TrackEdit.
// Non-blocking: track persisted; cover can be re-linked by re-editing.
Logger.LogWarning("Batch edit: cover link failed for new track '{TrackName}' (id={Id})",
row.TrackName, uploadResult.Value.Id);
}