using DeepDrftModels.DTOs; using DeepDrftPublic.Client.Services; using Microsoft.AspNetCore.Components; namespace DeepDrftPublic.Client.Controls; /// /// 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. /// public partial class ReleaseDetailScaffold : ComponentBase { [CascadingParameter] public IStreamingPlayerService? PlayerService { 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"; /// /// 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. /// [Parameter] public RenderFragment? TopContent { get; set; } /// /// 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. /// [Parameter] public RenderFragment? TopRightAction { get; set; } /// /// Optional replacement for the header region (masthead + play affordance). When null, the /// scaffold renders its default masthead+play row wired to . 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). /// [Parameter] public RenderFragment? Header { get; set; } /// Medium-specific hero visual (cover art, hero image, or waveform background). [Parameter] public RenderFragment? Hero { get; set; } /// /// 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 /// WaveformVisualizer — 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. /// [Parameter] public RenderFragment? Ambient { get; set; } /// /// Optional body region rendered below the meta block — the Cut album's multi-track listing. /// Single-track media leave it null. /// [Parameter] public RenderFragment? BodyContent { get; set; } /// Optional medium-specific metadata block, rendered under a divider when present. [Parameter] public RenderFragment? MetaContent { get; set; } /// /// Gate for the header region (masthead + play, or a custom ). 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 /// / (Phase 9 §5.3). Defaults to shown. /// [Parameter] public bool ShowHeader { get; set; } = true; /// /// Gate for the metadata block. Lets a consumer supply a fragment but /// suppress the divider + block when its data is empty (slot fragments cannot be conditionally /// attached inline). Defaults to shown. /// [Parameter] public bool ShowMeta { get; set; } = true; /// /// 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. /// [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 start a fresh // stream. SelectTrackStreaming is the live entry point — the buffered path is dead. var isThisTrack = PlayerService.CurrentTrack?.Id == Track.Id; if (isThisTrack && (PlayerService.IsPlaying || PlayerService.IsPaused)) { await PlayerService.TogglePlayPause(); } else { await PlayerService.SelectTrackStreaming(Track); } } }