958d472582
The component had a Dispose() method that detached ThemeState/LocaleState event handlers, but it was never invoked because the @implements directive was missing. Each navigation through the layout leaked two subscriptions on the singleton state objects. Other state subscribers (NavBody.razor, AnomalyFeed.razor) already declare the directive correctly.
140 lines
4.1 KiB
Plaintext
140 lines
4.1 KiB
Plaintext
@inherits LayoutComponentBase
|
|
@implements IDisposable
|
|
@inject ThemeState ThemeState
|
|
@inject LocaleState LocaleState
|
|
@inject IStringLocalizer<SharedResource> L
|
|
|
|
<MudThemeProvider IsDarkMode="@ThemeState.IsDark" Theme="@_theme" />
|
|
<MudPopoverProvider />
|
|
<MudDialogProvider FullWidth="true" MaxWidth="MaxWidth.Small" CloseOnEscapeKey="true" />
|
|
<MudSnackbarProvider />
|
|
|
|
<div class="m-app-frame @(_drawerOpen ? "is-drawer-open" : null)" data-theme="@(ThemeState.IsDark ? "dark" : "light")">
|
|
|
|
<header class="m-appbar">
|
|
<MudIconButton
|
|
Icon="@Icons.Material.Outlined.Menu"
|
|
Color="Color.Inherit"
|
|
Edge="Edge.Start"
|
|
OnClick="ToggleDrawer"
|
|
aria-label="@L["Nav.Section.Analysis"]" />
|
|
|
|
<AppBrand Class="m-rise m-rise-1" />
|
|
|
|
<div class="m-appbar__spacer"></div>
|
|
|
|
<div class="m-appbar__tools m-rise m-rise-2">
|
|
<LocaleSwitcher />
|
|
<ThemeToggle />
|
|
</div>
|
|
</header>
|
|
|
|
<MudDrawer
|
|
@bind-Open="_drawerOpen"
|
|
Anchor="Anchor.Left"
|
|
Variant="DrawerVariant.Responsive"
|
|
ClipMode="DrawerClipMode.Always"
|
|
Elevation="0"
|
|
Width="248px"
|
|
Color="Color.Dark">
|
|
<NavBody />
|
|
</MudDrawer>
|
|
|
|
<main class="m-main">
|
|
<CascadingValue Value="ThemeState">
|
|
<CascadingValue Value="LocaleState">
|
|
@Body
|
|
</CascadingValue>
|
|
</CascadingValue>
|
|
</main>
|
|
|
|
<footer class="m-footer">
|
|
<span class="m-kicker">Marathon Odds Lab</span>
|
|
<span style="font-family: var(--m-font-mono); font-size: 0.6875rem; color: var(--m-c-ink-soft); letter-spacing: 0.16em; text-transform: uppercase;">
|
|
Phase 5 · Editorial-Quant · v0.1
|
|
</span>
|
|
</footer>
|
|
</div>
|
|
|
|
<style>
|
|
.m-app-frame {
|
|
display: grid;
|
|
grid-template-rows: 60px 1fr 36px;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.m-appbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--m-space-3);
|
|
padding: 0 clamp(var(--m-space-3), 2vw, var(--m-space-5));
|
|
border-bottom: 1px solid var(--m-c-rule);
|
|
background: var(--m-c-paper);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
.m-appbar__spacer { flex: 1; }
|
|
.m-appbar__tools { display: inline-flex; gap: var(--m-space-3); align-items: center; }
|
|
|
|
.m-main {
|
|
position: relative;
|
|
z-index: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* MudDrawer is positioned fixed/absolute by Mud's CSS — push main content
|
|
right by the drawer's width (248px) so the sidebar doesn't overlap.
|
|
Same shift on the appbar tools row so the brand isn't covered. */
|
|
.m-app-frame.is-drawer-open .m-main,
|
|
.m-app-frame.is-drawer-open .m-footer {
|
|
padding-left: 248px;
|
|
transition: padding-left 200ms ease;
|
|
}
|
|
.m-app-frame.is-drawer-open .m-appbar {
|
|
padding-left: calc(248px + clamp(var(--m-space-3), 2vw, var(--m-space-5)));
|
|
transition: padding-left 200ms ease;
|
|
}
|
|
@@media (max-width: 720px) {
|
|
/* On narrow viewports the drawer becomes a temporary overlay anyway. */
|
|
.m-app-frame.is-drawer-open .m-main,
|
|
.m-app-frame.is-drawer-open .m-footer,
|
|
.m-app-frame.is-drawer-open .m-appbar { padding-left: 0; }
|
|
}
|
|
|
|
.m-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 clamp(var(--m-space-3), 2vw, var(--m-space-5));
|
|
border-top: 1px solid var(--m-c-rule);
|
|
background: var(--m-c-paper);
|
|
}
|
|
|
|
[data-theme="dark"] .m-appbar,
|
|
[data-theme="dark"] .m-footer {
|
|
background: var(--m-c-paper-2);
|
|
border-color: var(--m-c-rule);
|
|
}
|
|
</style>
|
|
|
|
@code {
|
|
private bool _drawerOpen = true;
|
|
private MudBlazor.MudTheme _theme = Theme.MarathonTheme.Build();
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
ThemeState.OnChange += StateHasChanged;
|
|
LocaleState.OnChange += StateHasChanged;
|
|
}
|
|
|
|
private void ToggleDrawer() => _drawerOpen = !_drawerOpen;
|
|
|
|
public void Dispose()
|
|
{
|
|
ThemeState.OnChange -= StateHasChanged;
|
|
LocaleState.OnChange -= StateHasChanged;
|
|
}
|
|
}
|