@namespace DeepDrftPublic.Client.Controls
@using DeepDrftModels.DTOs
@* Shared presentational queue list. Renders the ordered queue with the current track marked, and
(when Editable) drag-reorder handles + per-row remove controls. This is the single "view" both
the docked overlay (17.2) and the embedded panel (17.3) consume — one source, multiple views.
Purely presentational: owns no data fetch, no player wiring, and no IQueueService mutation of its
own. Order changes, removals, and row jumps are surfaced to the parent as EventCallbacks; the
parent calls the queue engine. It runs during prerender without JS interop (MudDropContainer's
drag work is client-only and inert when no drag occurs). *@
@if (Items is { Count: > 0 })
{
@if (Editable)
{
@RenderRow(context)
}
else
{
@foreach (var row in Rows)
{
@RenderRow(row)
}
}
}
@code {
/// The ordered tracks to render. Empty/null renders nothing.
[Parameter] public IReadOnlyList? Items { get; set; }
///
/// Index of the current track within , or -1 when none. The matching row is
/// rendered with a now-playing marker.
///
[Parameter] public int CurrentIndex { get; set; } = -1;
///
/// When true, rows show drag handles and a remove control and reorder is enabled. When false the
/// list is a read-only display (the embed's fixed-order shared queue).
///
[Parameter] public bool Editable { get; set; }
///
/// Raised when the user reorders a row: (fromIndex, toIndex). The parent calls
/// IQueueService.Move. Only fires when .
///
[Parameter] public EventCallback<(int FromIndex, int ToIndex)> OnReorder { get; set; }
///
/// Raised when the user removes a row, carrying the row's index. The parent calls
/// IQueueService.RemoveAt. Only fires when .
///
[Parameter] public EventCallback OnRemove { get; set; }
///
/// Raised when the user clicks a row body to jump playback to it, carrying the row's index. The
/// parent decides whether/how to honour it (e.g. play from that index).
///
[Parameter] public EventCallback OnJump { get; set; }
private MudDropContainer? _dropContainer;
// Index-tagged view rows. The index is the row's position in Items at render time and is the
// value surfaced to the parent's callbacks — the component never mutates the underlying list.
private List Rows =>
Items is null
? []
: Items.Select((track, index) => new QueueRow(index, track)).ToList();
private async Task OnItemDropped(MudItemDropInfo dropInfo)
{
var from = dropInfo.Item!.Index;
var to = dropInfo.IndexInZone;
// MudDropContainer recomputes the list from the parent's next render; refresh its snapshot so
// the dragged row snaps back until the parent's Move re-flows the cascaded Items.
_dropContainer?.Refresh();
if (from == to) return;
await OnReorder.InvokeAsync((from, to));
}
private sealed record QueueRow(int Index, TrackDto Track);
private RenderFragment RenderRow(QueueRow row) => __builder =>
{
var isCurrent = row.Index == CurrentIndex;
@if (Editable)
{
}
@(row.Index + 1)
OnJump.InvokeAsync(row.Index)">
@row.Track.TrackName
@if (row.Track.Release is { Artist: var artist } && !string.IsNullOrWhiteSpace(artist))
{
@artist
}
@if (isCurrent)
{
}
@if (Editable)
{
}
};
}