fa01b9c8e0
Toggle left of the lava popover hides release content so the visualizer fills the surface; player bar grows to carry the playing release's cover, title, and share. State on WaveformVisualizerControlState; pages and bar observe it.
135 lines
6.2 KiB
Plaintext
135 lines
6.2 KiB
Plaintext
@page "/sessions/{EntryKey}"
|
|
@using DeepDrftModels.DTOs
|
|
@using DeepDrftPublic.Client.Controls
|
|
@using DeepDrftPublic.Client.Services
|
|
@inherits ReleaseDetailBase
|
|
|
|
<PageTitle>@(ViewModel.Release?.Title ?? "Session") - DeepDrft</PageTitle>
|
|
|
|
@if (ViewModel.IsLoading)
|
|
{
|
|
<div class="deepdrft-track-detail-container">
|
|
<div class="deepdrft-track-detail-cover">
|
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Width="100%" Height="320px" />
|
|
</div>
|
|
<div class="deepdrft-track-detail-masthead">
|
|
<MudSkeleton SkeletonType="SkeletonType.Text" Width="70%" Height="56px" />
|
|
<MudSkeleton SkeletonType="SkeletonType.Text" Width="40%" Height="32px" />
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (ViewModel.NotFound || ViewModel.Release is null)
|
|
{
|
|
<div class="deepdrft-track-detail-container">
|
|
<div class="deepdrft-track-detail-masthead">
|
|
<MudText Typo="Typo.h4" Align="Align.Center">Session not found.</MudText>
|
|
<div class="d-flex justify-center mt-4">
|
|
<MudButton Href="/sessions"
|
|
Variant="Variant.Text"
|
|
StartIcon="@Icons.Material.Filled.ArrowBack">
|
|
All sessions
|
|
</MudButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
var release = ViewModel.Release;
|
|
var heroKey = release.SessionMetadata?.HeroImageEntryKey;
|
|
// Hero image precedence: the session's dedicated hero, then the release cover, then a placeholder.
|
|
var heroImage = !string.IsNullOrEmpty(heroKey) ? heroKey : release.ImagePath;
|
|
|
|
@* Ambient living waveform behind the hero overlay (Phase 12 §3e option b / §3f mode B). Session does
|
|
NOT compose ReleaseDetailScaffold, so it mounts the shared engine directly with its own thin
|
|
full-bleed wrapper — the engine is single-source either way, only the mount differs (§3b). The
|
|
visualizer positions itself fixed/inset:0; the session-detail-foreground class lifts the content
|
|
above it. The bridge follows the live playing track; TrackEntryKey is the at-rest datum. *@
|
|
<WaveformVisualizer ReleaseEntryKey="@release.EntryKey"
|
|
TrackId="@ViewModel.Track?.Id"
|
|
TrackEntryKey="@ViewModel.Track?.EntryKey" />
|
|
|
|
<MudContainer MaxWidth="MaxWidth.Large" Class="session-detail-page session-detail-foreground">
|
|
|
|
<div class="session-detail-top-row">
|
|
<MudLink Href="/sessions" Typo="Typo.body2" Class="deepdrft-track-detail-back">
|
|
← All sessions
|
|
</MudLink>
|
|
|
|
@* Theater toggle sits immediately LEFT of the lava-lamp popover (Phase 20 §3). The whole top
|
|
row (back + theater + lava) stays in Theater Mode — controls, not release content (§4/OQ4). *@
|
|
<div class="dd-detail-top-actions">
|
|
<TheaterModeToggle />
|
|
@* Lava-lamp icon → popover panel (full parity, §3e/§3d-revised). Anchored top-right, clear of
|
|
the hero overlay and the share/play affordances overlaid on the hero below. *@
|
|
<WaveformVisualizerControlPopover />
|
|
</div>
|
|
</div>
|
|
|
|
@* Theater Mode (Phase 20 §4): the hero overlay + blurb (the session's release content) are removed
|
|
from the render so the ambient visualizer fills the surface. The top row above stays. OFF
|
|
restores this region byte-for-byte. *@
|
|
@if (!VisualizerControlState.TheaterMode)
|
|
{
|
|
@* The overlay shows the cover thumbnail only when it differs from the resolved hero image —
|
|
when there is no dedicated hero, heroImage already falls back to release.ImagePath, so the
|
|
thumb would duplicate the background. That logic lives in ReleaseHeroOverlay. *@
|
|
<ReleaseHeroOverlay HeroImageKey="@heroImage"
|
|
CoverThumbKey="@release.ImagePath"
|
|
PlaceholderIcon="@Icons.Material.Filled.Piano"
|
|
Title="@release.Title"
|
|
Artist="@release.Artist"
|
|
Genre="@release.Genre"
|
|
ReleaseDate="@release.ReleaseDate">
|
|
<ShareContent>
|
|
@* Release-mode share: copies the canonical /sessions/{entryKey} URL, not a single track (§3b). *@
|
|
<SharePopover ReleaseEntryKey="@release.EntryKey" ReleaseMedium="@release.Medium" />
|
|
</ShareContent>
|
|
<PlayContent>
|
|
@if (ViewModel.Track is not null)
|
|
{
|
|
<PlayStateIcon Track="@ViewModel.Track" Size="Size.Large" Color="Color.Secondary" OnToggle="@PlayTrack" />
|
|
@* Append-only: queues the session's single track without starting playback. *@
|
|
<AddToQueueButton Track="@ViewModel.Track" Size="Size.Large" />
|
|
}
|
|
</PlayContent>
|
|
</ReleaseHeroOverlay>
|
|
|
|
<ReleaseDescription Description="@release.Description" />
|
|
}
|
|
|
|
</MudContainer>
|
|
}
|
|
|
|
@code {
|
|
protected override string PersistKey => "session-detail";
|
|
|
|
[CascadingParameter] public IStreamingPlayerService? PlayerService { get; set; }
|
|
[CascadingParameter] public IQueueService? Queue { get; set; }
|
|
|
|
// Mirrors the play-toggle wiring the shared scaffold owns. Session detail composes the player
|
|
// affordance directly (it diverges from ReleaseDetailScaffold for the overlay layout), so the
|
|
// toggle logic lives here: toggle if this track is already active, 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.
|
|
private async Task PlayTrack()
|
|
{
|
|
var track = ViewModel.Track;
|
|
if (track is null || PlayerService is null) return;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|