feat: CMS cover-art upload on track edit page
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
@page "/tracks/{Id:long}"
|
||||
@using DeepDrftManager.Services
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@attribute [Authorize]
|
||||
@inject ICmsTrackService CmsTrackService
|
||||
@inject IHttpClientFactory HttpClientFactory
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
@inject NavigationManager Nav
|
||||
@@ -60,6 +62,36 @@
|
||||
Label="Genre"
|
||||
Variant="Variant.Outlined" />
|
||||
|
||||
<MudField Label="Cover Art" Variant="Variant.Outlined" InnerPadding="false">
|
||||
<MudStack Spacing="3">
|
||||
@if (ImagePreviewUrl is { } previewUrl)
|
||||
{
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudImage Src="@previewUrl"
|
||||
Alt="Cover art preview"
|
||||
Elevation="1"
|
||||
Style="max-width: 120px; height: auto; border-radius: 4px;" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Clear"
|
||||
Color="Color.Error"
|
||||
Size="Size.Small"
|
||||
Disabled="_busy"
|
||||
OnClick="ClearImage"
|
||||
aria-label="Clear cover art" />
|
||||
</MudStack>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body2" Color="Color.Default">No cover art set.</MudText>
|
||||
}
|
||||
|
||||
<InputFile OnChange="HandleImageFileSelected" accept="image/*" />
|
||||
@if (_selectedImageFile is { } selected)
|
||||
{
|
||||
<MudText Typo="Typo.caption">Selected: @selected.Name (will upload on save)</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
</MudField>
|
||||
|
||||
<MudDatePicker @bind-Date="_form.ReleaseDate"
|
||||
Label="Release Date"
|
||||
DateFormat="yyyy-MM-dd"
|
||||
@@ -94,11 +126,25 @@
|
||||
private TrackEditForm _form = new();
|
||||
private bool _loading = true;
|
||||
private bool _busy;
|
||||
private IBrowserFile? _selectedImageFile;
|
||||
|
||||
private bool CanSave =>
|
||||
!string.IsNullOrWhiteSpace(_form.TrackName)
|
||||
&& !string.IsNullOrWhiteSpace(_form.Artist);
|
||||
|
||||
// The image endpoint (GET api/image/{entryKey}) is unauthenticated, so the browser can hit
|
||||
// DeepDrftAPI directly. Base address comes from the same named client the CMS uses for writes.
|
||||
private string? ImagePreviewUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_form.ImagePath)) return null;
|
||||
var baseAddress = HttpClientFactory.CreateClient("DeepDrft.Content.Cms").BaseAddress;
|
||||
if (baseAddress is null) return null;
|
||||
return new Uri(baseAddress, $"api/image/{Uri.EscapeDataString(_form.ImagePath)}").ToString();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAsync();
|
||||
@@ -123,14 +169,33 @@
|
||||
_busy = true;
|
||||
try
|
||||
{
|
||||
// Metadata-only update over HTTP — EntryKey is immutable and not sent. The Content
|
||||
// API loads the authoritative row and applies these fields.
|
||||
// Upload any newly picked cover art first; abort the save if it fails so we never
|
||||
// persist metadata pointing at an image that was never stored.
|
||||
if (_selectedImageFile is { } file)
|
||||
{
|
||||
await using var imageStream = file.OpenReadStream(maxAllowedSize: 50_000_000);
|
||||
var uploadResult = await CmsTrackService.UploadImageAsync(
|
||||
imageStream, file.Name, file.ContentType);
|
||||
if (!uploadResult.Success)
|
||||
{
|
||||
var uploadError = uploadResult.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
Snackbar.Add($"Image upload failed: {uploadError}", Severity.Error);
|
||||
return;
|
||||
}
|
||||
_form.ImagePath = uploadResult.Value;
|
||||
_selectedImageFile = null;
|
||||
}
|
||||
|
||||
// Metadata update over HTTP — EntryKey is immutable and not sent. The Content API
|
||||
// loads the authoritative row and applies these fields. imagePath is tri-state: an
|
||||
// explicit empty string clears the link, a value sets it.
|
||||
var releaseDate = _form.ReleaseDate is { } d ? DateOnly.FromDateTime(d) : (DateOnly?)null;
|
||||
var updated = await CmsTrackService.UpdateAsync(
|
||||
Id, _form.TrackName, _form.Artist,
|
||||
string.IsNullOrWhiteSpace(_form.Album) ? null : _form.Album,
|
||||
string.IsNullOrWhiteSpace(_form.Genre) ? null : _form.Genre,
|
||||
releaseDate);
|
||||
releaseDate,
|
||||
string.IsNullOrEmpty(_form.ImagePath) ? "" : _form.ImagePath);
|
||||
if (updated.Success)
|
||||
{
|
||||
Snackbar.Add("Track updated.", Severity.Success);
|
||||
@@ -153,6 +218,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleImageFileSelected(InputFileChangeEventArgs e)
|
||||
{
|
||||
_selectedImageFile = e.File;
|
||||
}
|
||||
|
||||
private void ClearImage()
|
||||
{
|
||||
_form.ImagePath = null;
|
||||
_selectedImageFile = null;
|
||||
}
|
||||
|
||||
private async Task ConfirmDelete()
|
||||
{
|
||||
if (_track is null) return;
|
||||
@@ -197,6 +273,7 @@
|
||||
public string Artist { get; set; } = string.Empty;
|
||||
public string? Album { get; set; }
|
||||
public string? Genre { get; set; }
|
||||
public string? ImagePath { get; set; }
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
public static TrackEditForm From(TrackDto track) => new()
|
||||
@@ -205,6 +282,7 @@
|
||||
Artist = track.Artist,
|
||||
Album = track.Album,
|
||||
Genre = track.Genre,
|
||||
ImagePath = track.ImagePath,
|
||||
ReleaseDate = track.ReleaseDate is { } d
|
||||
? d.ToDateTime(TimeOnly.MinValue)
|
||||
: null
|
||||
|
||||
Reference in New Issue
Block a user