diff --git a/LayerComponents/CascadingState.razor b/LayerComponents/CascadingState.razor index 95d91bb..bf82ecf 100644 --- a/LayerComponents/CascadingState.razor +++ b/LayerComponents/CascadingState.razor @@ -58,6 +58,13 @@ } } + /// Changes the page settings, then saves and applies them like UpdatePublicCache. + public ValueTask UpdatePageSettings(Action change) + { + change(PublicCacheData.PageSettings); + return UpdatePublicCache(PublicCacheData); + } + [JSInvokable] public async Task LogFromJs(string message, string where) { diff --git a/Models/PageSettings.cs b/Models/PageSettings.cs index f318d7c..ab3f8d0 100644 --- a/Models/PageSettings.cs +++ b/Models/PageSettings.cs @@ -1,4 +1,4 @@ - + using PrivaPub.ClientModels.Resources; using System.ComponentModel.DataAnnotations; @@ -24,5 +24,40 @@ namespace decePubClient.Models public bool ThemeIsDarkGray { get; set; } = false; public bool ShowDonatorBadge { get; set; } = false; + + // How the theme flags combine is in wwwroot/js/theme.js: with PreferSystemTheming the OS picks light or dark; + // otherwise ThemeIsDarkGray, then ThemeIsLightGray, then ThemeIsDarkMode decide; either grey flag drops the + // colour. These keep the flags consistent when the UI changes one aspect. + + public const short DefaultLightThemeIndexColour = 25; + public const short DefaultDarkThemeIndexColour = 215; + + public bool IsGray() => ThemeIsLightGray || ThemeIsDarkGray; + + /// Leaves system theming and shows the light or dark variant, grey or coloured as before. + public void SetDarkMode(bool isDark) + { + PreferSystemTheming = false; + ThemeIsDarkMode = isDark; + if (IsGray()) + SetGray(true, isDark); + } + + /// Switches grey on or off for the variant currently shown. + public void SetGray(bool isGray, bool isDark) + { + ThemeIsDarkGray = isGray && isDark; + ThemeIsLightGray = isGray && !isDark; + } + + /// Sets the hue of the light or dark variant; picking a hue means colour, so grey goes off. + public void SetHue(bool isDark, short hue) + { + if (isDark) + DarkThemeIndexColour = hue; + else + LightThemeIndexColour = hue; + SetGray(false, isDark); + } } } \ No newline at end of file diff --git a/Pages/Settings.razor b/Pages/Settings.razor index 480a953..e898458 100644 --- a/Pages/Settings.razor +++ b/Pages/Settings.razor @@ -6,17 +6,33 @@
- +

@Localizer["General"]

-
-

- @Localizer["Empty"] -

+ @* Every theme setting; NavMenu's toggle and slider are shortcuts to the dark and hue ones. *@ +
+ + + + +
@@ -72,4 +88,10 @@ @code { [CascadingParameter] CascadingState CascadingState { get; set; } + + PageSettings Theme => CascadingState.PublicCacheData?.PageSettings ?? new(); + bool IsDark => CascadingState.Status.IsDarkTheme(); + bool IsGray => Theme.IsGray(); + + Task Update(Action change) => CascadingState.UpdatePageSettings(change).AsTask(); } diff --git a/Services/AppStatusService.cs b/Services/AppStatusService.cs index 99e0e59..83cad2a 100644 --- a/Services/AppStatusService.cs +++ b/Services/AppStatusService.cs @@ -49,6 +49,8 @@ public class AppStatusService // wwwroot/js/theme.js public void ApplyTheme(PageSettings pageSettings) => JsSyncRuntime.InvokeVoid("neoTheme.apply", pageSettings); + public bool IsDarkTheme() => JsSyncRuntime.Invoke("neoTheme.isDark"); + public bool IsMobileMedia() => JsSyncRuntime.Invoke("isMobileMedia"); public bool CanShare() => JsSyncRuntime.Invoke("canShareStuff"); diff --git a/Shared/NavMenu.razor b/Shared/NavMenu.razor index 5399a67..ebc0ba1 100644 --- a/Shared/NavMenu.razor +++ b/Shared/NavMenu.razor @@ -74,7 +74,7 @@
- + @@ -104,21 +104,16 @@ @code { [CascadingParameter] CascadingState CascadingState { get; set; } string menuToggle = "hidden"; - bool IsThemeChanging { get; set; } = false; - bool ThemeIsDarkMode { get; set; } = false; - short ThemeIndexColour { get; set; } = 25; - protected override async Task OnInitializedAsync() + PageSettings Settings => CascadingState.PublicCacheData?.PageSettings ?? new(); + // The toggle and the slider work on the variant that is showing; with system theming that is the OS's choice. + bool IsDark => CascadingState.Status.IsDarkTheme(); + short ThemeIndexColour => IsDark ? Settings.DarkThemeIndexColour : Settings.LightThemeIndexColour; + + protected override void OnInitialized() { - await Task.Run(() => - { - }); if (!CascadingState.Status.IsMobileMedia()) menuToggle = default; - - ThemeIsDarkMode = CascadingState.PublicCacheData?.PageSettings.ThemeIsDarkMode ?? false; - ThemeIndexColour = ThemeIsDarkMode ? CascadingState.PublicCacheData?.PageSettings.DarkThemeIndexColour ?? 215 : - CascadingState.PublicCacheData?.PageSettings.LightThemeIndexColour ?? 25; } private void ToggleNavMenu() @@ -128,55 +123,39 @@ protected async Task ResetToOriginalColour() { - IsThemeChanging = true; - CascadingState.PublicCacheData.PageSettings.LightThemeIndexColour = - ThemeIndexColour = - 25; + var isDark = IsDark; + await CascadingState.UpdatePageSettings(settings => settings.SetHue(isDark, + isDark ? PageSettings.DefaultDarkThemeIndexColour : PageSettings.DefaultLightThemeIndexColour)); // if (AuthData?.User != null) // { // AuthData.User.UserSettings.ThemeIndexColour = ThemeIndexColour; // await Storage.SetItemAsync(nameof(AuthData), AuthData); // } - - await CascadingState.UpdatePublicCache(CascadingState.PublicCacheData); - - IsThemeChanging = false; } + // Picking a hue also leaves the grey themes, which otherwise ignore it. protected async Task UpdateThemeColour(short indexColour) { - IsThemeChanging = true; - CascadingState.PublicCacheData.PageSettings.LightThemeIndexColour = - ThemeIndexColour = - indexColour; + var isDark = IsDark; + await CascadingState.UpdatePageSettings(settings => settings.SetHue(isDark, indexColour)); // if (AuthData?.User != null) // { // AuthData.User.UserSettings.ThemeIndexColour = ThemeIndexColour; // await Storage.SetItemAsync(nameof(AuthData), AuthData); // } - - await CascadingState.UpdatePublicCache(CascadingState.PublicCacheData); - - IsThemeChanging = false; } + // Choosing light or dark here leaves system theming; Settings can turn it back on. protected async Task UpdateThemeDarkMode(bool isDarkMode) { - IsThemeChanging = true; - CascadingState.PublicCacheData.PageSettings.ThemeIsDarkMode = - ThemeIsDarkMode = isDarkMode; - Console.WriteLine("Dark updated {0}", ThemeIsDarkMode); + await CascadingState.UpdatePageSettings(settings => settings.SetDarkMode(isDarkMode)); // if (AuthData?.User != null) // { // AuthData.User.UserSettings.ThemeIsDarkMode = ThemeIsDarkMode; // await Storage.SetItemAsync(nameof(AuthData), AuthData); // } - - await CascadingState.UpdatePublicCache(CascadingState.PublicCacheData); - - IsThemeChanging = false; } } \ No newline at end of file diff --git a/wwwroot/js/theme.js b/wwwroot/js/theme.js index fce6aaf..6f13371 100644 --- a/wwwroot/js/theme.js +++ b/wwwroot/js/theme.js @@ -1,7 +1,8 @@ // Applies PageSettings to the page. It picks the daisyUI theme from Styles/theme.css and sets the hue and chroma // variables that theme derives every colour from. It loads in right after css/app.css and applies the // settings saved in localStorage before the first paint. Once Blazor runs, CascadingState calls neoTheme.apply -// through AppStatusService.ApplyTheme whenever the settings change. +// through AppStatusService.ApplyTheme whenever the settings change, and the theme controls ask neoTheme.isDark +// (AppStatusService.IsDarkTheme) which variant they are editing. (() => { const root = document.documentElement let current = null @@ -48,5 +49,9 @@ // With system theming the OS can switch between light and dark at any time. window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => apply(current)) - window.neoTheme = { apply } + // Whether the dark variant is showing, chosen or taken from the OS. + const isDark = () => root.getAttribute("data-theme") === "neo-dark" || + (!root.hasAttribute("data-theme") && window.matchMedia("(prefers-color-scheme: dark)").matches) + + window.neoTheme = { apply, isDark } })()