Files
EnvelopeGenerator/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Layout/MainLayout.razor
TekH 7fc2c14f9c Add dynamic theme and dark mode functionality
Introduced `ThemeService` to manage themes and dark mode, with a new footer UI for theme selection and toggling dark mode. Added dynamic CSS loading and Bootstrap theme support. Registered `UserPreferencesService` and `ThemeService` for SSR compatibility. Removed hardcoded DevExpress theme links.

Fixed German localization issues in `LoginReceiverPage` and `LoginSenderPage`. Updated `app.css` with styles for the new footer controls. Simplified font loading in `MainLayout.razor`.
2026-09-03 09:24:13 +02:00

91 lines
3.5 KiB
Plaintext

@using EnvelopeGenerator.Server.Client.Services
@inherits LayoutComponentBase
@implements IDisposable
@inject ThemeService ThemeService
<HeadContent>
<link href="@ThemeService.ThemeCssHref" rel="stylesheet" />
</HeadContent>
<div class="page" data-bs-theme="@ThemeService.BootstrapTheme">
<main>
<article class="content">
@Body
</article>
</main>
<footer class="receiver-footer">
<span>&copy; SignFlow 2023-2024 <a href="https://digitaldata.works" target="_blank" rel="noopener">Digital Data GmbH</a></span>
<span class="receiver-footer__sep">&#124;</span>
<a href="docs/privacy-policy.de-DE.html" target="_blank" rel="noopener">Datenschutz</a>
<div class="sf-footer-controls">
<select class="sf-footer-select"
title="Theme wählen"
@onchange="OnThemeChanged">
@foreach (var theme in ThemeService.AvailableThemes)
{
<option value="@theme.Value" selected="@(theme.Value == ThemeService.ThemeName)">
@theme.Label
</option>
}
</select>
<button class="sf-footer-toggle"
@onclick="OnToggleDarkMode"
title="@(ThemeService.IsDarkMode ? "Light Mode" : "Dark Mode")">
@if (ThemeService.IsDarkMode)
{
@* Sun *@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5" fill="currentColor" stroke="none"/>
<line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
}
else
{
@* Moon *@
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" viewBox="0 0 24 24">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
}
</button>
</div>
</footer>
</div>
@code {
[Inject] IHttpClientFactory HttpClientFactory { get; set; } = default!;
private Action? _themeChangeHandler;
protected override async Task OnInitializedAsync()
{
await FontLoader.LoadFonts(HttpClientFactory, ["opensans.ttf"]);
_themeChangeHandler = () => InvokeAsync(StateHasChanged);
ThemeService.OnChange += _themeChangeHandler;
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await ThemeService.InitializeAsync();
}
private async Task OnToggleDarkMode()
=> await ThemeService.ToggleDarkModeAsync();
private async Task OnThemeChanged(ChangeEventArgs e)
{
var value = e.Value?.ToString();
if (!string.IsNullOrEmpty(value))
await ThemeService.SetThemeAsync(value);
}
public void Dispose()
=> ThemeService.OnChange -= _themeChangeHandler;
}