134 lines
5.1 KiB
Plaintext
134 lines
5.1 KiB
Plaintext
@using DeepDrftPublic.Client.Controls
|
|
@using DeepDrftPublic.Client.Controls.AudioPlayerBar
|
|
@using DeepDrftPublic.Client.Services
|
|
@using DeepDrftPublic.Client.Common
|
|
@using DeepDrftShared.Client.Common
|
|
@using Microsoft.AspNetCore.Components
|
|
@inherits LayoutComponentBase
|
|
@implements IDisposable
|
|
@implements IAsyncDisposable
|
|
|
|
<MudThemeProvider Theme="@DeepDrftPalettes.Default" IsDarkMode="_isDarkMode" />
|
|
<MudPopoverProvider />
|
|
<MudDialogProvider />
|
|
<MudSnackbarProvider />
|
|
<div class="@ThemeWrapperClass">
|
|
<MudLayout Style="display: flex; flex-direction: column; min-height: 100vh">
|
|
<AudioPlayerProvider>
|
|
<DeepDrftMenu Elevation="4" @bind-IsDarkMode="_isDarkMode" />
|
|
<MudMainContent Class="flex-grow-1 pb-8 dd-main-content">
|
|
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
|
|
@Body
|
|
</MudContainer>
|
|
</MudMainContent>
|
|
<DeepDrftFooter />
|
|
<AudioPlayerBar OnMinimized="ToggleAudioPlayerMinimized" />
|
|
|
|
@* Spacer to prevent content overlap. Height tracks the fixed player
|
|
dock's live size via the --player-height var the player publishes
|
|
(see AudioPlayerBar / Interop/layout/spacer.ts). *@
|
|
<div class="player-spacer @_audioPlayerClass"></div>
|
|
</AudioPlayerProvider>
|
|
</MudLayout>
|
|
</div>
|
|
|
|
|
|
<div id="blazor-error-ui" data-nosnippet>
|
|
An unhandled error has occurred.
|
|
<a href="." class="reload">Reload</a>
|
|
<span class="dismiss">🗙</span>
|
|
</div>
|
|
|
|
@code {
|
|
private string _audioPlayerClass = "minimized";
|
|
private const string DarkModeKey = "darkMode";
|
|
private const string StreamQualityKey = "streamQuality";
|
|
private bool _isDarkMode = false;
|
|
private bool? _lastAppliedDarkMode = null;
|
|
private PersistingComponentStateSubscription _persistingSubscription;
|
|
private IJSObjectReference? _themeModule;
|
|
|
|
[Inject] public required PersistentComponentState PersistentState { get; set; }
|
|
[Inject] public required DarkModeSettings DarkModeSettings { get; set; }
|
|
[Inject] public required PublicSiteSettings PublicSiteSettings { get; set; }
|
|
[Inject] public required IJSRuntime JS { get; set; }
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
base.OnInitialized();
|
|
|
|
// Restore persisted dark mode state (from server prerender)
|
|
if (PersistentState.TryTakeFromJson<bool>(DarkModeKey, out var restored))
|
|
{
|
|
_isDarkMode = restored;
|
|
DarkModeSettings.IsDarkMode = restored;
|
|
}
|
|
else
|
|
{
|
|
_isDarkMode = DarkModeSettings.IsDarkMode;
|
|
}
|
|
|
|
// Restore the prerender-seeded streaming-quality preference (Phase 18 wave 18.6). Same bridge dark
|
|
// mode uses: the server SettingsService seeded PublicSiteSettings from the streamQuality cookie, and
|
|
// this carries it into WASM so the client boots already knowing the preference (no re-read flash, no
|
|
// wrong default before the first stream).
|
|
if (PersistentState.TryTakeFromJson<StreamQuality>(StreamQualityKey, out var restoredQuality))
|
|
{
|
|
PublicSiteSettings.StreamQuality = restoredQuality;
|
|
}
|
|
|
|
// Register to persist state when prerendering completes
|
|
_persistingSubscription = PersistentState.RegisterOnPersisting(PersistState);
|
|
}
|
|
|
|
// Sync dark mode class on <body> so portaled MudBlazor elements (popovers, menus, selects)
|
|
// inherit --deepdrft-popover-surface from body.deepdrft-theme-dark rather than from :root only.
|
|
// Popovers portal outside the ThemeWrapperClass div, so only a body-level class can reach them.
|
|
// Gated: only fires on first render or when _isDarkMode actually changes, to avoid redundant
|
|
// JS calls on unrelated re-renders (e.g. audio player minimize/expand).
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
|
|
if (firstRender || _isDarkMode != _lastAppliedDarkMode)
|
|
{
|
|
_lastAppliedDarkMode = _isDarkMode;
|
|
_themeModule ??= await JS.InvokeAsync<IJSObjectReference>(
|
|
"import", "./_content/DeepDrftShared.Client/js/theme/theme.js");
|
|
await _themeModule.InvokeVoidAsync("setBodyThemeClass", _isDarkMode);
|
|
}
|
|
}
|
|
|
|
// Theme wrapper class for CSS targeting
|
|
private string ThemeWrapperClass => _isDarkMode ? "deepdrft-theme-dark" : "deepdrft-theme-light";
|
|
|
|
private Task PersistState()
|
|
{
|
|
PersistentState.PersistAsJson(DarkModeKey, _isDarkMode);
|
|
PersistentState.PersistAsJson(StreamQualityKey, PublicSiteSettings.StreamQuality);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_persistingSubscription.Dispose();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_themeModule != null)
|
|
{
|
|
try { await _themeModule.DisposeAsync(); }
|
|
catch (JSDisconnectedException) { /* circuit torn down */ }
|
|
}
|
|
}
|
|
|
|
private void ToggleAudioPlayerMinimized(bool isMinimized)
|
|
{
|
|
_audioPlayerClass = isMinimized ? "minimized" : "expanded";
|
|
}
|
|
|
|
}
|
|
|
|
|