Apply the theme from PageSettings in JS, before the first paint
wwwroot/js/theme.js now picks the theme. It sets data-theme (neo-light or neo-dark, or none when the OS decides) and the hue and chroma variables on <html>, using the same flag precedence as the old CascadingState branches. It also keeps <meta name="theme-color"> in sync. Because it runs in <head> from the settings saved in localStorage, a reload no longer flashes the default palette before Blazor boots. With system theming it also follows the OS when the colour scheme changes. CascadingState loses its ~200 lines of generated <style> and <Meta> tags. It now calls AppStatusService.ApplyTheme on load and whenever the settings change. The --fa-* icon colour variables are gone; nothing read them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
5b5fd62233
commit
8e03d12a3e
4 files changed
+64
-203
No files matched your search
@@ -1,205 +1,4 @@
|
|||||||
@if (PublicCacheData != null)
|
<CascadingValue IsFixed=false Value=this>
|
||||||
{
|
|
||||||
if (PublicCacheData.PageSettings.ThemeIsDarkMode)
|
|
||||||
{
|
|
||||||
<Meta Content="@($"hsl({PublicCacheData.PageSettings.DarkThemeIndexColour},16%,12%)")" Name="theme-color"/>
|
|
||||||
<Meta Content="@($"hsl({PublicCacheData.PageSettings.DarkThemeIndexColour},16%,12%)")" Name="background-color"/>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<Meta Content="@($"hsl({PublicCacheData.PageSettings.LightThemeIndexColour},84%,88%)")" Name="theme-color"/>
|
|
||||||
<Meta Content="@($"hsl({PublicCacheData.PageSettings.LightThemeIndexColour},84%,88%)")" Name="background-color"/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<HeadContent>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@if (PublicCacheData != null)
|
|
||||||
{
|
|
||||||
var iconsColour = PublicCacheData.PageSettings.IconsThemeIndexColour is >= 0 and <= 359 ?
|
|
||||||
$"--fa-primary-color: hsl({PublicCacheData.PageSettings.IconsThemeIndexColour},84%,26.4%);--fa-secondary-color: hsl({PublicCacheData.PageSettings.IconsThemeIndexColour},84%,66%);" :
|
|
||||||
PublicCacheData.PageSettings.IconsThemeIndexColour is -2 ?
|
|
||||||
$"--fa-primary-color: hsl(0,0%,1%);--fa-secondary-color: hsl(0,0%,40%);" :
|
|
||||||
$"--fa-primary-color: hsl(0,0%,60%);--fa-secondary-color: hsl(0,0%,99%);";
|
|
||||||
@if (PublicCacheData.PageSettings.PreferSystemTheming)
|
|
||||||
{
|
|
||||||
if (PublicCacheData.PageSettings.ThemeIsDarkGray || PublicCacheData.PageSettings.ThemeIsLightGray)
|
|
||||||
{
|
|
||||||
@($@"@media screen and (prefers-color-scheme: light) {{
|
|
||||||
:root {{
|
|
||||||
--colour-index: 0;
|
|
||||||
--background: hsl(0,0%,88%);
|
|
||||||
--text-color: hsl(0,0%,26.4%);
|
|
||||||
--placeholder-text-color: hsla(0,0%,26.4%,.4);
|
|
||||||
--primary-color: hsl(0,0%,88%);
|
|
||||||
--primary-color-light: hsl(0,0%,100%);
|
|
||||||
--primary-color-dark: hsl(0,0%,66%);
|
|
||||||
--primary-gradiend-light: hsl(0,0%,92%);
|
|
||||||
--primary-gradiend-dark: hsl(0,0%,84%);
|
|
||||||
--primary-gradiend-lighter: hsl(0,0%,96%);
|
|
||||||
--primary-gradiend-darker: hsl(0,0%,80%);
|
|
||||||
--light-shadow: hsla(0,0%,100%, .5);
|
|
||||||
--dark-shadow: hsla(0,0%,66%, .5);
|
|
||||||
--danger-gradiend-light: hsl(0,0%,92%);
|
|
||||||
--danger-gradiend-dark: hsl(0,0%,84%);
|
|
||||||
{iconsColour}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
@media screen and (prefers-color-scheme: dark) {{
|
|
||||||
:root {{
|
|
||||||
--colour-index: 0;
|
|
||||||
--background: hsl(0,0%,12%);
|
|
||||||
--text-color: hsl(0,0%,73.6%);
|
|
||||||
--placeholder-text-color: hsla(0,0%,52.8%,.4);
|
|
||||||
--primary-color: hsl(0,0%,12%);
|
|
||||||
--primary-color-light: hsl(0,0%,100%);
|
|
||||||
--primary-color-dark: hsl(0,0%,33%);
|
|
||||||
--primary-gradiend-light: hsl(0,0%,16%);
|
|
||||||
--primary-gradiend-dark: hsl(0,0%,8%);
|
|
||||||
--primary-gradiend-lighter: hsl(0,0%,20%);
|
|
||||||
--primary-gradiend-darker: hsl(0,0%,4%);
|
|
||||||
--light-shadow: hsla(0,0%,66%,.1);
|
|
||||||
--dark-shadow: hsla(0,0%,1%,.5);
|
|
||||||
--danger-gradiend-light: hsl(0,16%,16%);
|
|
||||||
--danger-gradiend-dark: hsl(0,16%,8%);
|
|
||||||
{iconsColour}
|
|
||||||
}}
|
|
||||||
}}")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@($@"@media screen and (prefers-color-scheme: light) {{
|
|
||||||
:root {{
|
|
||||||
--colour-index: {PublicCacheData.PageSettings.LightThemeIndexColour};
|
|
||||||
--background: hsl(var(--colour-index,25),84%,88%);
|
|
||||||
--text-color: hsl(var(--colour-index,25),84%,26.4%);
|
|
||||||
--placeholder-text-color: hsla(var(--colour-index,25),84%,26.4%,.4);
|
|
||||||
--primary-color: hsl(var(--colour-index,25),84%,88%);
|
|
||||||
--primary-color-light: hsl(var(--colour-index,25),84%,100%);
|
|
||||||
--primary-color-dark: hsl(var(--colour-index,25),84%,66%);
|
|
||||||
--primary-gradiend-light: hsl(var(--colour-index,25),84%,92%);
|
|
||||||
--primary-gradiend-dark: hsl(var(--colour-index,25),84%,84%);
|
|
||||||
--primary-gradiend-lighter: hsl(var(--colour-index,25),84%,96%);
|
|
||||||
--primary-gradiend-darker: hsl(var(--colour-index,25),84%,80%);
|
|
||||||
--light-shadow: hsla(var(--colour-index,25),84%,100%, .5);
|
|
||||||
--dark-shadow: hsla(var(--colour-index,25),84%,66%, .5);
|
|
||||||
--danger-gradiend-light: hsl(0,84%,92%);
|
|
||||||
--danger-gradiend-dark: hsl(0,84%,84%);
|
|
||||||
{iconsColour}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
|
|
||||||
@media screen and (prefers-color-scheme: dark) {{
|
|
||||||
:root {{
|
|
||||||
--colour-index: {PublicCacheData.PageSettings.DarkThemeIndexColour};
|
|
||||||
--background: hsl(var(--colour-index,215),16%,12%);
|
|
||||||
--text-color: hsl(var(--colour-index,215),16%,73.6%);
|
|
||||||
--placeholder-text-color: hsla(var(--colour-index,25),84%,26.4%,.4);
|
|
||||||
--primary-color: hsl(var(--colour-index,215),16%,12%);
|
|
||||||
--primary-color-light: hsl(var(--colour-index,215),84%,100%);
|
|
||||||
--primary-color-dark: hsl(var(--colour-index,215),16%,33%);
|
|
||||||
--primary-gradiend-light: hsl(var(--colour-index,215),16%,16%);
|
|
||||||
--primary-gradiend-dark: hsl(var(--colour-index,215),16%,8%);
|
|
||||||
--primary-gradiend-lighter: hsl(var(--colour-index,215),16%,20%);
|
|
||||||
--primary-gradiend-darker: hsl(var(--colour-index,215),16%,4%);
|
|
||||||
--light-shadow: hsla(var(--colour-index,215),84%,66%,.1);
|
|
||||||
--dark-shadow: hsla(var(--colour-index,215),16%,1%,.5);
|
|
||||||
--danger-gradiend-light: hsl(0,16%,16%);
|
|
||||||
--danger-gradiend-dark: hsl(0,16%,8%);
|
|
||||||
{iconsColour}
|
|
||||||
}}
|
|
||||||
}}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (PublicCacheData.PageSettings.ThemeIsDarkGray || PublicCacheData.PageSettings.ThemeIsLightGray)
|
|
||||||
{
|
|
||||||
if (PublicCacheData.PageSettings.ThemeIsDarkGray)
|
|
||||||
{
|
|
||||||
@($@":root{{--colour-index: 0;
|
|
||||||
--background: hsl(0,0%,12%);
|
|
||||||
--text-color: hsl(0,0%,73.6%);
|
|
||||||
--placeholder-text-color: hsla(0,0%,73.6%,.4);
|
|
||||||
--primary-color: hsl(0,0%,12%);
|
|
||||||
--primary-color-light: hsl(0,0%,100%);
|
|
||||||
--primary-color-dark: hsl(0,0%,33%);
|
|
||||||
--primary-gradiend-light: hsl(0,0%,16%);
|
|
||||||
--primary-gradiend-dark: hsl(0,0%,8%);
|
|
||||||
--primary-gradiend-lighter: hsl(0,0%,20%);
|
|
||||||
--primary-gradiend-darker: hsl(0,0%,4%);
|
|
||||||
--light-shadow: hsla(0,0%,66%,.1);
|
|
||||||
--dark-shadow: hsla(0,0%,1%,.5);
|
|
||||||
--danger-gradiend-light: hsl(0,16%,16%);
|
|
||||||
--danger-gradiend-dark: hsl(0,16%,8%);{iconsColour}}}")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@($@":root{{--colour-index: 0;
|
|
||||||
--background: hsl(0,0%,88%);
|
|
||||||
--text-color: hsl(0,0%,26.4%);
|
|
||||||
--placeholder-text-color: hsla(0,0%,26.4%,.4);
|
|
||||||
--primary-color: hsl(0,0%,88%);
|
|
||||||
--primary-color-light: hsl(0,0%,100%);
|
|
||||||
--primary-color-dark: hsl(0,0%,66%);
|
|
||||||
--primary-gradiend-light: hsl(0,0%,92%);
|
|
||||||
--primary-gradiend-dark: hsl(0,0%,84%);
|
|
||||||
--primary-gradiend-lighter: hsl(0,0%,96%);
|
|
||||||
--primary-gradiend-darker: hsl(0,0%,80%);
|
|
||||||
--light-shadow: hsla(0,0%,100%, .5);
|
|
||||||
--dark-shadow: hsla(0,0%,66%, .5);
|
|
||||||
--danger-gradiend-light: hsl(0,84%,92%);
|
|
||||||
--danger-gradiend-dark: hsl(0,84%,84%);{iconsColour}}}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (PublicCacheData.PageSettings.ThemeIsDarkMode)
|
|
||||||
{
|
|
||||||
@($@":root{{--colour-index: {PublicCacheData.PageSettings.DarkThemeIndexColour};
|
|
||||||
--background: hsl(var(--colour-index,215),16%,12%);
|
|
||||||
--text-color: hsl(var(--colour-index,215),16%,73.6%);
|
|
||||||
--placeholder-text-color: hsla(var(--colour-index,215),16%,73.6%,.4);
|
|
||||||
--primary-color: hsl(var(--colour-index,215),16%,12%);
|
|
||||||
--primary-color-light: hsl(var(--colour-index,215),84%,100%);
|
|
||||||
--primary-color-dark: hsl(var(--colour-index,215),16%,33%);
|
|
||||||
--primary-gradiend-light: hsl(var(--colour-index,215),16%,16%);
|
|
||||||
--primary-gradiend-dark: hsl(var(--colour-index,215),16%,8%);
|
|
||||||
--primary-gradiend-lighter: hsl(var(--colour-index,215),16%,20%);
|
|
||||||
--primary-gradiend-darker: hsl(var(--colour-index,215),16%,4%);
|
|
||||||
--light-shadow: hsla(var(--colour-index,215),84%,66%,.1);
|
|
||||||
--dark-shadow: hsla(var(--colour-index,215),16%,1%,.5);
|
|
||||||
--danger-gradiend-light: hsl(0,16%,16%);
|
|
||||||
--danger-gradiend-dark: hsl(0,16%,8%);{iconsColour}}}")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@($@":root{{--colour-index: {PublicCacheData.PageSettings.LightThemeIndexColour};
|
|
||||||
--background: hsl(var(--colour-index,25),84%,88%);
|
|
||||||
--text-color: hsl(var(--colour-index,25),84%,26.4%);
|
|
||||||
--placeholder-text-color: hsla(var(--colour-index,25),84%,26.4%,.4);
|
|
||||||
--primary-color: hsl(var(--colour-index,25),84%,88%);
|
|
||||||
--primary-color-light: hsl(var(--colour-index,25),84%,100%);
|
|
||||||
--primary-color-dark: hsl(var(--colour-index,25),84%,66%);
|
|
||||||
--primary-gradiend-light: hsl(var(--colour-index,25),84%,92%);
|
|
||||||
--primary-gradiend-dark: hsl(var(--colour-index,25),84%,84%);
|
|
||||||
--primary-gradiend-lighter: hsl(var(--colour-index,25),84%,96%);
|
|
||||||
--primary-gradiend-darker: hsl(var(--colour-index,25),84%,80%);
|
|
||||||
--light-shadow: hsla(var(--colour-index,25),84%,100%, .5);
|
|
||||||
--dark-shadow: hsla(var(--colour-index,25),84%,66%, .5);
|
|
||||||
--danger-gradiend-light: hsl(0,84%,92%);
|
|
||||||
--danger-gradiend-dark: hsl(0,84%,84%);{iconsColour}}}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</HeadContent>
|
|
||||||
|
|
||||||
<CascadingValue IsFixed=false Value=this>
|
|
||||||
@ChildContent
|
@ChildContent
|
||||||
</CascadingValue>
|
</CascadingValue>
|
||||||
|
|
||||||
@@ -232,6 +31,8 @@
|
|||||||
PublicCacheData = new();
|
PublicCacheData = new();
|
||||||
await UpdatePublicCache(PublicCacheData);
|
await UpdatePublicCache(PublicCacheData);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
Status.ApplyTheme(PublicCacheData.PageSettings);
|
||||||
|
|
||||||
User = new();
|
User = new();
|
||||||
}
|
}
|
||||||
@@ -247,6 +48,7 @@
|
|||||||
{
|
{
|
||||||
PublicCacheData = publicCacheData;
|
PublicCacheData = publicCacheData;
|
||||||
await Storage.SetItemAsync(nameof(PublicCacheData), PublicCacheData);
|
await Storage.SetItemAsync(nameof(PublicCacheData), PublicCacheData);
|
||||||
|
Status.ApplyTheme(PublicCacheData.PageSettings);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Microsoft.JSInterop;
|
using decePubClient.Models;
|
||||||
|
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
namespace decePubClient.Services;
|
namespace decePubClient.Services;
|
||||||
|
|
||||||
@@ -44,6 +46,9 @@ public class AppStatusService
|
|||||||
JsSyncRuntime.InvokeVoid("reloadPage");
|
JsSyncRuntime.InvokeVoid("reloadPage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wwwroot/js/theme.js
|
||||||
|
public void ApplyTheme(PageSettings pageSettings) => JsSyncRuntime.InvokeVoid("neoTheme.apply", pageSettings);
|
||||||
|
|
||||||
public bool IsMobileMedia() => JsSyncRuntime.Invoke<bool>("isMobileMedia");
|
public bool IsMobileMedia() => JsSyncRuntime.Invoke<bool>("isMobileMedia");
|
||||||
|
|
||||||
public bool CanShare() => JsSyncRuntime.Invoke<bool>("canShareStuff");
|
public bool CanShare() => JsSyncRuntime.Invoke<bool>("canShareStuff");
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
<link href="vendor/ionicons.min.css" rel="stylesheet" />
|
<link href="vendor/ionicons.min.css" rel="stylesheet" />
|
||||||
<link href="css/app.css" rel="stylesheet" />
|
<link href="css/app.css" rel="stylesheet" />
|
||||||
|
<meta name="theme-color" content="hsl(0 0% 88%)" />
|
||||||
|
<script src="js/theme.js"></script>
|
||||||
<link href="manifest.json" rel="manifest" />
|
<link href="manifest.json" rel="manifest" />
|
||||||
<link rel="apple-touch-icon" sizes="512x512" href="imgs/icon-512.png" />
|
<link rel="apple-touch-icon" sizes="512x512" href="imgs/icon-512.png" />
|
||||||
<link rel="apple-touch-icon" sizes="192x192" href="imgs/icon-192.png" />
|
<link rel="apple-touch-icon" sizes="192x192" href="imgs/icon-192.png" />
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// 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 <head> 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.
|
||||||
|
(() => {
|
||||||
|
const root = document.documentElement
|
||||||
|
let current = null
|
||||||
|
|
||||||
|
// The same precedence the old CascadingState <style> block had:
|
||||||
|
// - System theming leaves data-theme off, so daisyUI's prefersdark rule decides.
|
||||||
|
// - Otherwise dark grey, then light grey, then dark mode, then light.
|
||||||
|
// - Either grey flag drops the chroma to 0.
|
||||||
|
const apply = (settings) => {
|
||||||
|
current = settings || {}
|
||||||
|
const system = current.preferSystemTheming ?? true
|
||||||
|
const darkGray = current.themeIsDarkGray ?? false
|
||||||
|
const lightGray = current.themeIsLightGray ?? true
|
||||||
|
const dark = current.themeIsDarkMode ?? false
|
||||||
|
|
||||||
|
const theme = system ? null : darkGray ? "neo-dark" : lightGray ? "neo-light" : dark ? "neo-dark" : "neo-light"
|
||||||
|
if (theme)
|
||||||
|
root.setAttribute("data-theme", theme)
|
||||||
|
else
|
||||||
|
root.removeAttribute("data-theme")
|
||||||
|
|
||||||
|
root.style.setProperty("--neo-hue-light", current.lightThemeIndexColour ?? 25)
|
||||||
|
root.style.setProperty("--neo-hue-dark", current.darkThemeIndexColour ?? 215)
|
||||||
|
root.style.setProperty("--neo-chroma", darkGray || lightGray ? 0 : 1)
|
||||||
|
|
||||||
|
// daisyUI paints the root with base-100, so its computed background is the resolved theme colour.
|
||||||
|
let meta = document.querySelector('meta[name="theme-color"]')
|
||||||
|
if (!meta) {
|
||||||
|
meta = document.createElement("meta")
|
||||||
|
meta.name = "theme-color"
|
||||||
|
document.head.appendChild(meta)
|
||||||
|
}
|
||||||
|
meta.content = getComputedStyle(root).backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cache = JSON.parse(localStorage.getItem("PublicCacheData") || "null")
|
||||||
|
apply(cache && cache.pageSettings)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("theme.js", e)
|
||||||
|
apply(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 }
|
||||||
|
})()
|
||||||
Reference in new issue
Block a user