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:
@@ -10,6 +10,15 @@ else
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="player-inner-container">
|
||||
<MudPaper Elevation="8" Class="player-surface pa-3">
|
||||
|
||||
@* Theater Mode "now showing" band (Phase 20 §5/§7). Shown only when Theater is ON and a
|
||||
release is playing — keyed off the playing track's Release, not off any detail page
|
||||
(the bar reaches into no page; §6). The release page is hidden in Theater Mode, so the
|
||||
bar carries its identity: cover, linked title, release share. *@
|
||||
@if (VisualizerControlState.TheaterMode && CurrentTrack?.Release is not null)
|
||||
{
|
||||
<NowShowingPanel Release="CurrentTrack.Release" />
|
||||
}
|
||||
|
||||
<div class="player-layout">
|
||||
<PlayerTransportZone IsLoaded="IsLoaded"
|
||||
CanPlay="CanPlay"
|
||||
|
||||
@@ -16,12 +16,18 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
|
||||
|
||||
[Inject] private IJSRuntime JsRuntime { get; set; } = default!;
|
||||
|
||||
// Theater Mode (Phase 20). Property-injected (no constructor growth) so the bar can read
|
||||
// TheaterMode to mount the "now showing" band and re-render when the flag flips. The toggle lives on
|
||||
// the detail pages; the bar only observes — single source, multiple observers (§6).
|
||||
[Inject] private WaveformVisualizerControlState VisualizerControlState { get; set; } = default!;
|
||||
|
||||
private bool _isMinimized = true;
|
||||
private bool _isSeeking = false;
|
||||
private double _seekPosition = 0;
|
||||
private bool _queueOpen = false;
|
||||
private IStreamingPlayerService? _subscribedService;
|
||||
private IQueueService? _subscribedQueue;
|
||||
private bool _subscribedToVisualizerState;
|
||||
|
||||
// Spacer-height bridge: the expanded dock is position:fixed, so MainLayout's
|
||||
// spacer reserves its space. We mirror this element's live height into a CSS
|
||||
@@ -143,8 +149,19 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
|
||||
QueueService.QueueChanged += OnQueueChanged;
|
||||
_subscribedQueue = QueueService;
|
||||
}
|
||||
|
||||
// Theater Mode (Phase 20 §7): re-render the bar when TheaterMode flips so the "now showing" band
|
||||
// appears/disappears. VisualizerControlState is injected (one stable scoped instance per session),
|
||||
// so the subscribe is once-only — same idempotent subscribe-here / unsubscribe-on-dispose shape.
|
||||
if (!_subscribedToVisualizerState)
|
||||
{
|
||||
VisualizerControlState.Changed += OnVisualizerStateChanged;
|
||||
_subscribedToVisualizerState = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVisualizerStateChanged() => InvokeAsync(StateHasChanged);
|
||||
|
||||
private void OnPlayerStateChanged() => InvokeAsync(StateHasChanged);
|
||||
|
||||
private void OnQueueChanged()
|
||||
@@ -402,6 +419,12 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
|
||||
_subscribedQueue = null;
|
||||
}
|
||||
|
||||
if (_subscribedToVisualizerState)
|
||||
{
|
||||
VisualizerControlState.Changed -= OnVisualizerStateChanged;
|
||||
_subscribedToVisualizerState = false;
|
||||
}
|
||||
|
||||
if (_spacerModule is not null)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -56,6 +56,54 @@
|
||||
color: var(--mud-palette-primary);
|
||||
}
|
||||
|
||||
/* Theater Mode "now showing" band (Phase 20 §5/§7). Sits above the transport layout inside the
|
||||
player surface and lets the bar grow taller to carry the hidden release's identity. The band only
|
||||
renders when Theater is ON, so this geometry is gated by render-inclusion, not a CSS flag — when
|
||||
Theater is OFF the player bar is byte-for-byte its non-Theater self.
|
||||
Colour/surface come from the bar's themed --deepdrft-page-* aliases; no new token, no dark override. */
|
||||
::deep .now-showing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--deepdrft-page-text-muted);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Fixed cover box — the reused .deepdrft-track-detail-cover-art / -placeholder idioms are height:100%,
|
||||
so the band supplies the square frame they fill. */
|
||||
::deep .now-showing-cover {
|
||||
flex: 0 0 auto;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::deep .now-showing-cover-art,
|
||||
::deep .now-showing-cover-placeholder {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::deep .now-showing-cover-placeholder .mud-icon-root {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
::deep .now-showing-title-link {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
::deep .now-showing-title {
|
||||
color: var(--deepdrft-page-text);
|
||||
}
|
||||
|
||||
::deep .now-showing-share {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* Minimized floating dock — positioning + hover only; colour from MudFab */
|
||||
.minimized-dock {
|
||||
position: fixed;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
@namespace DeepDrftPublic.Client.Controls.AudioPlayerBar
|
||||
@using DeepDrftModels.DTOs
|
||||
@using DeepDrftPublic.Client.Common
|
||||
@using DeepDrftPublic.Client.Controls
|
||||
|
||||
@* "Now showing" block surfaced in the player bar when Theater Mode is ON (Phase 20 §5/§7). Theater
|
||||
hides the release page, so the bar carries the release identity the page would have shown: cover art,
|
||||
the release title linked to its detail page, and a release-mode share. Purely presentational — it owns
|
||||
no player logic and no Theater state; AudioPlayerBar mounts it only when state.TheaterMode &&
|
||||
CurrentTrack?.Release is not null, so Release is non-null here.
|
||||
|
||||
Theming is all reuse (§8, zero new CSS): the cover reuses the deepdrft-track-detail-cover-art /
|
||||
-placeholder idiom; the share glyph goes green-accent in both themes via .dd-accent-icon; surface and
|
||||
text come from the bar's own .player-surface and the .now-showing-* classes in the global sheet, which
|
||||
bind the theme-aware --deepdrft-page-* aliases. *@
|
||||
|
||||
<div class="now-showing">
|
||||
<div class="now-showing-cover">
|
||||
@if (!string.IsNullOrEmpty(Release.ImagePath))
|
||||
{
|
||||
<div class="deepdrft-track-detail-cover-art now-showing-cover-art"
|
||||
style="@($"background-image: url('api/image/{Uri.EscapeDataString(Release.ImagePath)}');")"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="deepdrft-track-detail-cover-placeholder deepdrft-gradient-soft-secondary now-showing-cover-placeholder">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Album" Color="Color.Primary" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<a href="@ReleaseRoutes.DetailHref(Release)" class="now-showing-title-link">
|
||||
<MudText Typo="Typo.subtitle2" Class="now-showing-title text-truncate">
|
||||
@Release.Title
|
||||
</MudText>
|
||||
</a>
|
||||
|
||||
<div class="dd-accent-icon now-showing-share">
|
||||
<SharePopover ReleaseEntryKey="@Release.EntryKey" ReleaseMedium="@Release.Medium" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>The current playing track's release. Non-null by the bar's mount gate.</summary>
|
||||
[Parameter, EditorRequired] public ReleaseDto Release { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@namespace DeepDrftPublic.Client.Controls
|
||||
@using DeepDrftPublic.Client.Services
|
||||
@implements IDisposable
|
||||
@inject WaveformVisualizerControlState State
|
||||
|
||||
@* Theater-Mode toggle (Phase 20 §3). The single affordance placed identically on all three release
|
||||
detail pages — immediately to the LEFT of the lava-lamp WaveformVisualizerControlPopover trigger.
|
||||
It is purely a mutation surface: tapping it flips State.TheaterMode and raises Changed; the detail
|
||||
pages observe that to gate their content @if, and the player bar observes it to grow. This component
|
||||
reaches into no page and no bar — single source, multiple observers (§6).
|
||||
|
||||
Visible only when the lava OR waveform subsystem is on — there is nothing to go to theater FOR if both
|
||||
are off (§3.2). Disabled until interactive (§3.4), the same prerender guard the lava/Play buttons use.
|
||||
Active visual state when Theater is ON. .dd-accent-icon gives the green-accent glyph in both themes
|
||||
with zero new CSS (§8) — same treatment as the lava-lamp trigger it sits beside. *@
|
||||
|
||||
@if (State.LavaEnabled || State.WaveformEnabled)
|
||||
{
|
||||
<div class="dd-accent-icon">
|
||||
<MudTooltip Text="@(State.TheaterMode ? "Exit theater mode" : "Theater mode")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Theaters"
|
||||
Size="@IconSize"
|
||||
Color="Color.Secondary"
|
||||
Disabled="@(!RendererInfo.IsInteractive)"
|
||||
OnClick="@Toggle"
|
||||
aria-label="Theater mode"
|
||||
aria-pressed="@State.TheaterMode" />
|
||||
</MudTooltip>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>Trigger-icon size. Defaults Large to match the lava-lamp popover trigger it sits beside.</summary>
|
||||
[Parameter] public Size IconSize { get; set; } = Size.Large;
|
||||
|
||||
protected override void OnInitialized() => State.Changed += OnStateChanged;
|
||||
|
||||
// The toggle's own visibility and active state both key off State, which another observer (or this
|
||||
// button) may mutate, so re-render on every Changed — same idempotent posture the visualizer bridge uses.
|
||||
private void OnStateChanged() => InvokeAsync(StateHasChanged);
|
||||
|
||||
private void Toggle()
|
||||
{
|
||||
State.TheaterMode = !State.TheaterMode;
|
||||
State.NotifyChanged();
|
||||
}
|
||||
|
||||
public void Dispose() => State.Changed -= OnStateChanged;
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -97,6 +97,13 @@ public sealed class WaveformVisualizerControlState
|
||||
/// </summary>
|
||||
public const bool DefaultWaveformEnabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// Default Theater-mode state. <c>false</c> so a fresh page load opens with the full release page,
|
||||
/// not the bare visualizer (Phase 20 §4/OQ5). Has no TS-side anchor: Theater Mode is a page-chrome
|
||||
/// presentation flag, not a visualizer dial — the bridge never reads it.
|
||||
/// </summary>
|
||||
public const bool DefaultTheaterMode = false;
|
||||
|
||||
/// <summary>Apparent bottom-to-top scroll rate, normalized [0,1]. Bridge maps it to a visible
|
||||
/// time-span via <see cref="WaveformZoomMapping"/>; the standalone resolution/zoom control is gone.</summary>
|
||||
public double ScrollSpeed { get; set; } = DefaultScrollSpeed;
|
||||
@@ -137,9 +144,19 @@ public sealed class WaveformVisualizerControlState
|
||||
/// </summary>
|
||||
public bool WaveformEnabled { get; set; } = DefaultWaveformEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Whether Theater Mode is on (Phase 20). When <c>true</c> the three release-detail pages remove
|
||||
/// their release content via <c>@if</c> so the visualizer fills the surface, and the player bar
|
||||
/// grows to carry the playing release's identity. Distinct from the visualizer dials: the bridge
|
||||
/// ignores it — the pages and the player bar observe it through the same <see cref="Changed"/> seam.
|
||||
/// Gated for visibility on <see cref="LavaEnabled"/> || <see cref="WaveformEnabled"/> at the toggle.
|
||||
/// </summary>
|
||||
public bool TheaterMode { get; set; } = DefaultTheaterMode;
|
||||
|
||||
/// <summary>
|
||||
/// Raised whenever any control value changes. The visualizer bridge subscribes to push the
|
||||
/// affected dial(s). Mutators set the property then raise this; subscribers re-read the values.
|
||||
/// affected dial(s); the Theater-Mode observers (detail pages, player bar) subscribe to react to
|
||||
/// <see cref="TheaterMode"/>. Mutators set the property then raise this; subscribers re-read the values.
|
||||
/// </summary>
|
||||
public event Action? Changed;
|
||||
|
||||
|
||||
@@ -351,6 +351,15 @@ h2, h3, h4, h5, h6,
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Theater toggle + lava-lamp popover cluster on the detail-page top action row (Phase 20 §3). Keeps
|
||||
the two icon affordances adjacent on the right edge rather than letting the SpaceBetween row spread
|
||||
them apart. Shared by Cut/Mix (scaffold TopRightAction) and Session (its own top row). */
|
||||
.dd-detail-top-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.deepdrft-track-detail-meta {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
Reference in New Issue
Block a user