Files
deepdrft/DeepDrftPublic.Client/Controls/ReleaseDetailScaffold.razor.cs

119 lines
5.7 KiB
C#

using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Services;
using Microsoft.AspNetCore.Components;
namespace DeepDrftPublic.Client.Controls;
/// <summary>
/// Shared detail-page chrome for any release medium: back link, masthead, play/share affordance,
/// and hero/meta slots. Owns the play-toggle wiring against the cascaded streaming player so each
/// detail page supplies only its data and medium-specific visuals. Each medium's detail page is a
/// thin consumer of this scaffold.
/// </summary>
public partial class ReleaseDetailScaffold : ComponentBase
{
[CascadingParameter] public IStreamingPlayerService? PlayerService { get; set; }
[CascadingParameter] public IQueueService? Queue { get; set; }
[Parameter] public required string Title { get; set; }
[Parameter] public string? Artist { get; set; }
// The playable track for this release. Null while unresolved (or when a release has no
// streamable track yet) — the play/share row is hidden in that case.
[Parameter] public TrackDto? Track { get; set; }
[Parameter] public string BackHref { get; set; } = "/archive";
[Parameter] public string BackLabel { get; set; } = "Archive";
/// <summary>
/// Optional medium-specific content rendered as its own full-width band between the back/action top
/// row and the masthead — the "below the back button, above the details" band. The Mix detail page
/// uses it for the visualizer knob row (gated by Blazor on the lava-lamp toggle); other media leave
/// it null.
/// </summary>
[Parameter] public RenderFragment? TopContent { get; set; }
/// <summary>
/// Optional action rendered at the top-right of the container, on the same SpaceBetween row as the
/// back link (back link left, action right). The Mix detail page uses it for the lava-lamp
/// visualizer-settings button; other media leave it null and render the back link alone.
/// </summary>
[Parameter] public RenderFragment? TopRightAction { get; set; }
/// <summary>
/// Optional replacement for the header region (masthead + play affordance). When null, the
/// scaffold renders its default masthead+play row wired to <see cref="PlayTrack"/>. A composer
/// that needs a different header arrangement (e.g. the Cut album's left-meta / right-cover split
/// with its own Play/Share buttons) supplies this — layout variance rides the slot, never a
/// boolean flag (Phase 9 §5.3).
/// </summary>
[Parameter] public RenderFragment? Header { get; set; }
/// <summary>Medium-specific hero visual (cover art, hero image, or waveform background).</summary>
[Parameter] public RenderFragment? Hero { get; set; }
/// <summary>
/// Optional full-bleed ambient layer rendered BEHIND the scaffold content (Phase 12 §3c/§3f mode B).
/// A host that wants a living environment behind hero+content — e.g. Cut supplying a
/// <c>WaveformVisualizer</c> — places it here. The mounted layer positions itself fixed/inset:0
/// (its own CSS), so the scaffold only promotes its content into a foreground stacking context above
/// it. Absent = today's plain background, no regression (Liskov). Mode A (Mix) and mode C (the
/// NowPlaying card) mount the engine without this slot — see §3f.
/// </summary>
[Parameter] public RenderFragment? Ambient { get; set; }
/// <summary>
/// Optional body region rendered below the meta block — the Cut album's multi-track listing.
/// Single-track media leave it null.
/// </summary>
[Parameter] public RenderFragment? BodyContent { get; set; }
/// <summary>Optional medium-specific metadata block, rendered under a divider when present.</summary>
[Parameter] public RenderFragment? MetaContent { get; set; }
/// <summary>
/// Gate for the header region (masthead + play, or a custom <see cref="Header"/>). A composer that
/// carries title/artist/play elsewhere — e.g. Mix overlays them on its hero — sets this false to
/// suppress the duplicate. A gate, not a layout flag, so it is slot-consistent with
/// <see cref="ShowMeta"/> / <see cref="ShowShareRow"/> (Phase 9 §5.3). Defaults to shown.
/// </summary>
[Parameter] public bool ShowHeader { get; set; } = true;
/// <summary>
/// Gate for the metadata block. Lets a consumer supply a <see cref="MetaContent"/> fragment but
/// suppress the divider + block when its data is empty (slot fragments cannot be conditionally
/// attached inline). Defaults to shown.
/// </summary>
[Parameter] public bool ShowMeta { get; set; } = true;
/// <summary>
/// Gate for the default track-keyed share row at the foot of the scaffold. A composer that owns
/// its own share affordance (the Cut header carries Play + Share inline) sets this false to
/// suppress the duplicate. Defaults to shown.
/// </summary>
[Parameter] public bool ShowShareRow { get; set; } = true;
private async Task PlayTrack()
{
if (Track is null || PlayerService is null) return;
// Toggle if this track is already active (playing or paused); otherwise PLAY it —
// prepend to the queue's front (deque PLAY semantics) so it becomes current and
// the existing queue stays intact behind it. Falls back to a direct stream when
// the queue cascade is absent (prerender / non-interactive).
var isThisTrack = PlayerService.CurrentTrack?.Id == Track.Id;
if (isThisTrack && (PlayerService.IsPlaying || PlayerService.IsPaused))
{
await PlayerService.TogglePlayPause();
}
else if (Queue is not null)
{
await Queue.PlayTrack(Track);
}
else
{
await PlayerService.SelectTrackStreaming(Track);
}
}
}