feat(cms): compose Session hero image into the upload form (8.F)
Session upload now carries a deferred hero-image input; the submit handler creates the release then POSTs the held hero to the existing resource-addressed endpoint. Hero is optional with a non-blocking warn-if-missing gate. The per-row hero upload in CmsSessionBrowser remains the replace/correct path.
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
Medium="_medium"
|
||||
MediumChanged="OnMediumChanged"
|
||||
@bind-SelectedImageFile="_selectedImageFile"
|
||||
@bind-HeroImageFile="_heroImageFile"
|
||||
Disabled="_uploading" />
|
||||
|
||||
@if (_medium == ReleaseMedium.Cut)
|
||||
@@ -108,6 +109,12 @@
|
||||
private IBrowserFile? _selectedImageFile;
|
||||
private string? _imagePath;
|
||||
|
||||
// Session-only: the hero image is resource-addressed and cannot be uploaded until the release
|
||||
// exists, so it is held here and POSTed to api/release/{id}/session/hero-image after create.
|
||||
private IBrowserFile? _heroImageFile;
|
||||
// Set true once the admin has acknowledged the missing-hero warning, so a second submit proceeds.
|
||||
private bool _heroWarningAcknowledged;
|
||||
|
||||
private string _albumName = string.Empty;
|
||||
private string _artist = string.Empty;
|
||||
private string _genre = string.Empty;
|
||||
@@ -244,6 +251,18 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// A Session's hero is its primary visual identity on the public detail page. It is optional —
|
||||
// a Session can be authored without one and set later from the Sessions browser — but a missing
|
||||
// hero is usually an oversight, so warn (do not block). The first submit without a hero shows the
|
||||
// warning and primes acknowledgment; a second submit proceeds.
|
||||
if (_medium == ReleaseMedium.Session && _heroImageFile is null && !_heroWarningAcknowledged)
|
||||
{
|
||||
_heroWarningAcknowledged = true;
|
||||
_errorMessage = "No hero image selected. A Session usually needs one — you can add it now, "
|
||||
+ "or submit again to create the Session without it (set the hero later from the Sessions browser).";
|
||||
return;
|
||||
}
|
||||
|
||||
// For single-track media (Session/Mix) the track name is derived from the Release Name —
|
||||
// no separate Track Name input is shown. Sync here so the stored name always matches.
|
||||
if (MediumRules.CardinalityOf(_medium).IsSingleTrack && _tracks.Count > 0)
|
||||
@@ -336,6 +355,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Session hero image is resource-addressed, so it is uploaded here — after the
|
||||
// release exists and we have its id — within the same submit gesture. Non-blocking:
|
||||
// the Session is persisted; a failed hero upload is recoverable from the Sessions
|
||||
// browser's per-row Set/Replace hero action.
|
||||
if (_medium == ReleaseMedium.Session
|
||||
&& _heroImageFile is { } heroFile
|
||||
&& result.Value.ReleaseId is { } sessionReleaseId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using var heroStream = heroFile.OpenReadStream(maxAllowedSize: 50_000_000);
|
||||
var heroResult = await CmsReleaseService.UploadSessionHeroImageAsync(
|
||||
sessionReleaseId, heroStream, heroFile.Name, heroFile.ContentType);
|
||||
if (!heroResult.Success)
|
||||
{
|
||||
var heroError = heroResult.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
Logger.LogWarning("Batch upload: hero image upload failed for release {ReleaseId} ('{TrackName}'): {Error}",
|
||||
sessionReleaseId, row.TrackName, heroError);
|
||||
Snackbar.Add("Session uploaded, but the hero image failed. Set it from the Sessions browser.", Severity.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception heroEx)
|
||||
{
|
||||
Logger.LogError(heroEx, "Batch upload: exception uploading hero image for release {ReleaseId}", sessionReleaseId);
|
||||
Snackbar.Add("Session uploaded, but the hero image failed. Set it from the Sessions browser.", Severity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
// Mix uploads fire the server-side high-res waveform trigger (§3.4). The CMS
|
||||
// computes nothing — the API derives the datum from the audio it just stored.
|
||||
// Non-blocking: the track is persisted; a failed trigger is recoverable from
|
||||
|
||||
Reference in New Issue
Block a user