@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)
@row.Track.TrackName @if (row.Track.Release is { Artist: var artist } && !string.IsNullOrWhiteSpace(artist)) { @artist }
@if (isCurrent) { } @* The current track cannot be removed (OQ3/OQ11): the queue empties only organically as the current ends with nothing after it. Suppress the × on the current row only — reorder of the current track is still allowed. *@ @if (Editable && !isCurrent) { }
}; }