feat(public): add Theater Mode to release detail pages
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.
This commit is contained in:
@@ -54,11 +54,21 @@ else
|
||||
TrackEntryKey="@firstTrack?.EntryKey" />
|
||||
</Ambient>
|
||||
<TopRightAction>
|
||||
@* Lava-lamp icon → popover panel (full parity, §3d-revised). Sits top-right across from the
|
||||
back link, clear of the header's own Play/Share affordances below. *@
|
||||
<WaveformVisualizerControlPopover />
|
||||
@* Theater toggle sits immediately LEFT of the lava-lamp popover (Phase 20 §3). Both are
|
||||
controls over the experience, not release content, so both stay in Theater Mode (§4/OQ4).
|
||||
Wrapped so they cluster on the right rather than spreading across the SpaceBetween row. *@
|
||||
<div class="dd-detail-top-actions">
|
||||
<TheaterModeToggle />
|
||||
@* Lava-lamp icon → popover panel (full parity, §3d-revised). Sits top-right across from the
|
||||
back link, clear of the header's own Play/Share affordances below. *@
|
||||
<WaveformVisualizerControlPopover />
|
||||
</div>
|
||||
</TopRightAction>
|
||||
<Header>
|
||||
@* Theater Mode (Phase 20 §4): the release content is removed from the render — not
|
||||
CSS-hidden — so the visualizer fills the surface. OFF restores it byte-for-byte. *@
|
||||
@if (!VisualizerControlState.TheaterMode)
|
||||
{
|
||||
@* Header split: meta + Play/Share on the LEFT, bordered cover on the RIGHT (spec §3.1). *@
|
||||
<div class="cut-detail-header">
|
||||
<div class="cut-detail-meta">
|
||||
@@ -117,8 +127,11 @@ else
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Header>
|
||||
<BodyContent>
|
||||
@if (!VisualizerControlState.TheaterMode)
|
||||
{
|
||||
@* Blurb sits between the header and the track-list divider. *@
|
||||
<ReleaseDescription Description="@release.Description" />
|
||||
<MudDivider Class="cut-detail-divider" />
|
||||
@@ -149,6 +162,7 @@ else
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</BodyContent>
|
||||
</ReleaseDetailScaffold>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using DeepDrftModels.DTOs;
|
||||
using DeepDrftPublic.Client.Services;
|
||||
using DeepDrftPublic.Client.ViewModels;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -19,6 +20,10 @@ public abstract class CutDetailBase : ComponentBase, IDisposable
|
||||
[Inject] public required CutDetailViewModel ViewModel { get; set; }
|
||||
[Inject] public required PersistentComponentState PersistentState { get; set; }
|
||||
|
||||
// Theater Mode (Phase 20). The page owns the @if (!VisualizerControlState.TheaterMode) content gate,
|
||||
// so it must re-render when the flag flips on the toggle. Property-injected; no constructor growth.
|
||||
[Inject] public required WaveformVisualizerControlState VisualizerControlState { get; set; }
|
||||
|
||||
private PersistingComponentStateSubscription _persistingSubscription;
|
||||
|
||||
// The release EntryKey the ViewModel currently holds — tracks param-only navigations (e.g.
|
||||
@@ -28,7 +33,12 @@ public abstract class CutDetailBase : ComponentBase, IDisposable
|
||||
private bool _loaded;
|
||||
|
||||
protected override void OnInitialized()
|
||||
=> _persistingSubscription = PersistentState.RegisterOnPersisting(Persist);
|
||||
{
|
||||
_persistingSubscription = PersistentState.RegisterOnPersisting(Persist);
|
||||
VisualizerControlState.Changed += OnVisualizerStateChanged;
|
||||
}
|
||||
|
||||
private void OnVisualizerStateChanged() => InvokeAsync(StateHasChanged);
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
@@ -61,7 +71,11 @@ public abstract class CutDetailBase : ComponentBase, IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose() => _persistingSubscription.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
_persistingSubscription.Dispose();
|
||||
VisualizerControlState.Changed -= OnVisualizerStateChanged;
|
||||
}
|
||||
|
||||
// JSON-serializable bridge payload. Round-trips through PersistentComponentState's serializer.
|
||||
protected sealed record BridgedCut(ReleaseDto Release, IReadOnlyList<TrackDto> Tracks);
|
||||
|
||||
@@ -56,13 +56,23 @@ else
|
||||
ShowMeta="false"
|
||||
ShowShareRow="false">
|
||||
<TopRightAction>
|
||||
@* Lava-lamp icon → popover panel, top-right across from the back link (Phase 12
|
||||
§3d-revised). Replaces the former inline TopContent knob-bar: the icon IS the toggle
|
||||
and the popover IS the panel. Mix takes the cleanest anchor case (§8e) — the popover's
|
||||
default bottom-right anchor opens down over the full-bleed field. *@
|
||||
<WaveformVisualizerControlPopover />
|
||||
@* Theater toggle sits immediately LEFT of the lava-lamp popover (Phase 20 §3). Both stay
|
||||
visible in Theater Mode — controls over the experience, not release content (§4/OQ4).
|
||||
Wrapped so they cluster on the right rather than spreading across the SpaceBetween row. *@
|
||||
<div class="dd-detail-top-actions">
|
||||
<TheaterModeToggle />
|
||||
@* Lava-lamp icon → popover panel, top-right across from the back link (Phase 12
|
||||
§3d-revised). Replaces the former inline TopContent knob-bar: the icon IS the toggle
|
||||
and the popover IS the panel. Mix takes the cleanest anchor case (§8e) — the popover's
|
||||
default bottom-right anchor opens down over the full-bleed field. *@
|
||||
<WaveformVisualizerControlPopover />
|
||||
</div>
|
||||
</TopRightAction>
|
||||
<Hero>
|
||||
@* Theater Mode (Phase 20 §4): the hero overlay (Session/Mix release content) is removed
|
||||
from the render so the full-bleed visualizer fills the surface. OFF restores it. *@
|
||||
@if (!VisualizerControlState.TheaterMode)
|
||||
{
|
||||
@* Cover-as-background hero with all metadata overlaid, square `mix-hero` sizing. The
|
||||
cover art IS the background, so no separate cover thumbnail (CoverThumbKey defaults
|
||||
to null). Share and play ride in as slots, matching Sessions. *@
|
||||
@@ -86,10 +96,14 @@ else
|
||||
}
|
||||
</PlayContent>
|
||||
</ReleaseHeroOverlay>
|
||||
}
|
||||
</Hero>
|
||||
<BodyContent>
|
||||
@* Blurb sits below the hero, inside the scaffold's foreground stacking context. *@
|
||||
<ReleaseDescription Description="@release.Description" />
|
||||
@if (!VisualizerControlState.TheaterMode)
|
||||
{
|
||||
@* Blurb sits below the hero, inside the scaffold's foreground stacking context. *@
|
||||
<ReleaseDescription Description="@release.Description" />
|
||||
}
|
||||
</BodyContent>
|
||||
</ReleaseDetailScaffold>
|
||||
</MudContainer>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using DeepDrftModels.DTOs;
|
||||
using DeepDrftPublic.Client.Services;
|
||||
using DeepDrftPublic.Client.ViewModels;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -17,6 +18,10 @@ public abstract class ReleaseDetailBase : ComponentBase, IDisposable
|
||||
[Inject] public required ReleaseDetailViewModel ViewModel { get; set; }
|
||||
[Inject] public required PersistentComponentState PersistentState { get; set; }
|
||||
|
||||
// Theater Mode (Phase 20). The page owns the @if (!VisualizerControlState.TheaterMode) content gate,
|
||||
// so it must re-render when the flag flips on the toggle. Property-injected; no constructor growth.
|
||||
[Inject] public required WaveformVisualizerControlState VisualizerControlState { get; set; }
|
||||
|
||||
private PersistingComponentStateSubscription _persistingSubscription;
|
||||
|
||||
// The release EntryKey the ViewModel currently holds. Tracks param-only navigations (e.g.
|
||||
@@ -30,7 +35,12 @@ public abstract class ReleaseDetailBase : ComponentBase, IDisposable
|
||||
protected abstract string PersistKey { get; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
=> _persistingSubscription = PersistentState.RegisterOnPersisting(Persist);
|
||||
{
|
||||
_persistingSubscription = PersistentState.RegisterOnPersisting(Persist);
|
||||
VisualizerControlState.Changed += OnVisualizerStateChanged;
|
||||
}
|
||||
|
||||
private void OnVisualizerStateChanged() => InvokeAsync(StateHasChanged);
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
@@ -69,7 +79,11 @@ public abstract class ReleaseDetailBase : ComponentBase, IDisposable
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose() => _persistingSubscription.Dispose();
|
||||
public void Dispose()
|
||||
{
|
||||
_persistingSubscription.Dispose();
|
||||
VisualizerControlState.Changed -= OnVisualizerStateChanged;
|
||||
}
|
||||
|
||||
// JSON-serializable bridge payload. Round-trips through PersistentComponentState's serializer.
|
||||
protected sealed record BridgedDetail(ReleaseDto Release, TrackDto? Track);
|
||||
|
||||
@@ -56,11 +56,21 @@ else
|
||||
← All sessions
|
||||
</MudLink>
|
||||
|
||||
@* 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 />
|
||||
@* 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. *@
|
||||
@@ -86,6 +96,7 @@ else
|
||||
</ReleaseHeroOverlay>
|
||||
|
||||
<ReleaseDescription Description="@release.Description" />
|
||||
}
|
||||
|
||||
</MudContainer>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user