diff --git a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj index acf0224f659..fb6f05ea55d 100644 --- a/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj +++ b/src/BootstrapBlazor.Server/BootstrapBlazor.Server.csproj @@ -31,7 +31,7 @@ - + diff --git a/src/BootstrapBlazor.Server/Components/Components/Header.razor b/src/BootstrapBlazor.Server/Components/Components/Header.razor index abf04151a31..5a2200b9615 100644 --- a/src/BootstrapBlazor.Server/Components/Components/Header.razor +++ b/src/BootstrapBlazor.Server/Components/Components/Header.razor @@ -48,5 +48,5 @@ @DownloadText - + diff --git a/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs b/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs index 42252c1801e..3ffe421f37e 100644 --- a/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Components/Header.razor.cs @@ -57,6 +57,4 @@ protected override void OnInitialized() TutorialsText ??= Localizer[nameof(TutorialsText)]; _versionString = $"v{PackageVersionService.Version}"; } - - private Task OnThemeChangedAsync(ThemeValue themeName) => InvokeVoidAsync("updateTheme", themeName, WebsiteOption.CurrentValue.AssetRootPath); } diff --git a/src/BootstrapBlazor.Server/Components/Components/Header.razor.js b/src/BootstrapBlazor.Server/Components/Components/Header.razor.js index 204e7bbeda6..f678cd30842 100644 --- a/src/BootstrapBlazor.Server/Components/Components/Header.razor.js +++ b/src/BootstrapBlazor.Server/Components/Components/Header.razor.js @@ -1,5 +1,4 @@ -import { switchTheme as syncTheme } from "./Pre.razor.js" -import EventHandler from "../../_content/BootstrapBlazor/modules/event-handler.js" +import EventHandler from "../../_content/BootstrapBlazor/modules/event-handler.js" export function init(id) { const scrollTop = () => (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop @@ -17,10 +16,6 @@ export function init(id) { }); } -export async function updateTheme(theme, assetPath) { - await syncTheme(theme, assetPath); -} - export function dispose(id) { EventHandler.off(document, 'scroll'); } diff --git a/src/BootstrapBlazor.Server/Components/Components/Pre.razor.cs b/src/BootstrapBlazor.Server/Components/Components/Pre.razor.cs index 7077a5f9adc..90133402d80 100644 --- a/src/BootstrapBlazor.Server/Components/Components/Pre.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Components/Pre.razor.cs @@ -29,6 +29,10 @@ public partial class Pre [NotNull] private CodeSnippetService? CodeSnippetService { get; set; } + [Inject] + [NotNull] + private IThemeProvider? ThemeProviderService { get; set; } + /// /// 获得/设置 子组件 CodeFile 为空时生效 /// @@ -67,6 +71,16 @@ public partial class Pre private string? CopiedText { get; set; } + /// + /// + /// + protected override void OnInitialized() + { + base.OnInitialized(); + + ThemeProviderService.ThemeChangedAsync += OnThemeChanged; + } + /// /// /// @@ -189,4 +203,12 @@ private string FindCodeSnippetByName(string code) [GeneratedRegex(@"[\s\S]*?")] private static partial Regex TipsRegex(); + + private async Task OnThemeChanged(string themeName) + { + if (themeName == "light" || themeName == "dark") + { + await InvokeVoidAsync("switchTheme", themeName); + } + } } diff --git a/src/BootstrapBlazor.Server/Components/Components/Pre.razor.js b/src/BootstrapBlazor.Server/Components/Components/Pre.razor.js index 35247026b4e..54d82b2be68 100644 --- a/src/BootstrapBlazor.Server/Components/Components/Pre.razor.js +++ b/src/BootstrapBlazor.Server/Components/Components/Pre.razor.js @@ -78,14 +78,14 @@ export async function highlight(id) { } } -export async function switchTheme(theme, assetPath) { +export async function switchTheme(theme) { if (theme === 'dark') { - removeLink(`${assetPath}lib/highlight/vs.min.css`); - await addLink(`${assetPath}lib/highlight/vs2015.min.css`); + removeLink(`./lib/highlight/vs.min.css`); + await addLink(`./lib/highlight/vs2015.min.css`); } else { - removeLink(`${assetPath}lib/highlight/vs2015.min.css`); - await addLink(`${assetPath}lib/highlight/vs.min.css`); + removeLink(`./lib/highlight/vs2015.min.css`); + await addLink(`./lib/highlight/vs.min.css`); } } diff --git a/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor b/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor index a1b658e6549..3021f368488 100644 --- a/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor +++ b/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor @@ -1,5 +1,5 @@ @inherits WebSiteModuleComponentBase -@attribute [JSModuleAutoLoader("Components/ThemeMode.razor.js")] +@attribute [JSModuleAutoLoader("Components/ThemeMode.razor.js", JSObjectReference = true)]
diff --git a/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.cs b/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.cs index f996d2122dd..783caf74ba2 100644 --- a/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.cs +++ b/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone +using Microsoft.JSInterop; + namespace BootstrapBlazor.Server.Components.Components; /// @@ -13,6 +15,10 @@ public partial class ThemeMode [Inject, NotNull] private IIconTheme? IconTheme { get; set; } + [Inject] + [NotNull] + private IThemeProvider? ThemeProvider { get; set; } + private string? GetLightIconClassString => CssBuilder.Default("icon-light") .AddClass(_lightModeIcon) .Build(); @@ -35,4 +41,17 @@ protected override void OnInitialized() _darkModeIcon ??= IconTheme.GetIconByKey(ComponentIcons.ThemeProviderDarkModeIcon); _lightModeIcon ??= IconTheme.GetIconByKey(ComponentIcons.ThemeProviderLightModeIcon); } + + /// + /// + /// + protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(OnThemeChanged)); + + /// + /// The callback when theme changed + /// + /// + /// + [JSInvokable] + public ValueTask OnThemeChanged(string themeName) => ThemeProvider.SetThemeAsync(themeName); } diff --git a/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.js b/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.js index f0a7962ba3b..ce6ed515de3 100644 --- a/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.js +++ b/src/BootstrapBlazor.Server/Components/Components/ThemeMode.razor.js @@ -1,7 +1,7 @@ import { getTheme, switchTheme, calcCenterPosition } from "../../_content/BootstrapBlazor/modules/utility.js" import EventHandler from "../../_content/BootstrapBlazor/modules/event-handler.js" -export function init(id) { +export function init(id, invoke, method) { const el = document.getElementById(id); if (el) { EventHandler.on(el, 'click', e => { @@ -15,6 +15,7 @@ export function init(id) { const rect = calcCenterPosition(el); switchTheme(theme, rect.x, rect.y); + invoke.invokeMethodAsync(method, theme); }); } } diff --git a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/BaseDockView.cs b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/BaseDockView.cs index b43fb66ad7d..ca40bad886f 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/BaseDockView.cs +++ b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/BaseDockView.cs @@ -22,6 +22,10 @@ public abstract class BaseDockView : ComponentBase [NotNull] private MockDataTableDynamicService? DataTableDynamicService { get; set; } + [Inject] + [NotNull] + private IThemeProvider? ThemeProviderService { get; set; } + /// /// 获得/设置 数据集合 /// @@ -39,6 +43,11 @@ public abstract class BaseDockView : ComponentBase /// protected DataTableDynamicContext? DataTableDynamicContext { get; set; } + /// + /// Gets or sets the theme + /// + protected DockViewTheme Theme { get; set; } = DockViewTheme.Light; + /// /// 获得 实例方法 /// @@ -51,6 +60,8 @@ public abstract class BaseDockView : ComponentBase /// protected override void OnInitialized() { + base.OnInitialized(); + Items = Foo.GenerateFoo(LocalizerFoo, 50); // 模拟数据从数据库中获得 @@ -63,6 +74,22 @@ protected override void OnInitialized() TreeItems.AddRange(TreeFoo.GenerateFoos(LocalizerFoo, 3, 101, 1010)); DataTableDynamicContext = DataTableDynamicService.CreateContext(); + + ThemeProviderService.ThemeChangedAsync += OnThemeChanged; + } + + private Task OnThemeChanged(string themeName) + { + if (themeName == "dark") + { + Theme = DockViewTheme.Dark; + } + else + { + Theme = DockViewTheme.Light; + } + StateHasChanged(); + return Task.CompletedTask; } /// diff --git a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewCol.razor b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewCol.razor index 422522d84ad..38632f3419e 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewCol.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewCol.razor @@ -5,7 +5,7 @@

@((MarkupString)Localizer["DockViewColIntro"].Value)

- + @((MarkupString)Localizer["DockViewComplexIntro"].Value) - + diff --git a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewGroup.razor b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewGroup.razor index 891e1af7d83..3e5e85ad94b 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewGroup.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewGroup.razor @@ -5,7 +5,7 @@

@((MarkupString)Localizer["DockViewGroupIntro"].Value)

- + diff --git a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewLayout.razor b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewLayout.razor index 08fffe29a95..8d469704886 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewLayout.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewLayout.razor @@ -12,7 +12,7 @@ - +
- +
@((MarkupString)Localizer["DockViewNestIntro"].Value) - + - +
- +
? Localizer { get; set; } - - private DockViewTheme _theme; } diff --git a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewRow.razor b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewRow.razor index e5bb196c9e9..e53c5225a57 100644 --- a/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewRow.razor +++ b/src/BootstrapBlazor.Server/Components/Samples/DockViews2/DockViewRow.razor @@ -5,7 +5,7 @@

@((MarkupString)Localizer["DockViewRowIntro"].Value)

- +
@((MarkupString)Localizer["DockViewTitleIntro"].Value) - +
- +
? Localizer { get; set; } + [Inject] + [NotNull] + private IThemeProvider? ThemeProviderService { get; set; } + private string? ClassString => CssBuilder.Default("dropdown bb-theme-mode") .AddClassFromAttributes(AdditionalAttributes) .Build(); @@ -139,5 +143,6 @@ public async Task OnThemeChanged(ThemeValue name) { await OnThemeChangedAsync(name); } + await ThemeProviderService.SetThemeAsync(name.ToDescriptionString()); } } diff --git a/src/BootstrapBlazor/Components/ThemeProvider/ThemeValue.cs b/src/BootstrapBlazor/Components/ThemeProvider/ThemeValue.cs index 3276c2354dd..5305bcfc357 100644 --- a/src/BootstrapBlazor/Components/ThemeProvider/ThemeValue.cs +++ b/src/BootstrapBlazor/Components/ThemeProvider/ThemeValue.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone +using System.ComponentModel; + namespace BootstrapBlazor.Components; /// @@ -14,20 +16,24 @@ public enum ThemeValue /// /// 自动 /// + [Description("auto")] Auto, /// /// 明亮主题 /// + [Description("light")] Light, /// /// 暗黑主题 /// + [Description("dark")] Dark, /// /// 使用本地保存选项 /// + [Description("useLocalStorage")] UseLocalStorage, } diff --git a/src/BootstrapBlazor/Services/DefaultThemeProvider.cs b/src/BootstrapBlazor/Services/DefaultThemeProvider.cs index 9fddf9c6bd6..397a7b4800a 100644 --- a/src/BootstrapBlazor/Services/DefaultThemeProvider.cs +++ b/src/BootstrapBlazor/Services/DefaultThemeProvider.cs @@ -7,6 +7,11 @@ namespace BootstrapBlazor.Components; class DefaultThemeProvider(IJSRuntime jsRuntime) : IThemeProvider { + /// + /// + /// + public Func? ThemeChangedAsync { get; set; } + /// /// /// @@ -15,6 +20,11 @@ public async ValueTask SetThemeAsync(string themeName) { var module = await jsRuntime.LoadUtility(); await module.SetThemeAsync(themeName); + + if (ThemeChangedAsync is not null) + { + await ThemeChangedAsync(themeName); + } } /// diff --git a/src/BootstrapBlazor/Services/IThemeProvider.cs b/src/BootstrapBlazor/Services/IThemeProvider.cs index f2c0fc03bba..070c9040cdb 100644 --- a/src/BootstrapBlazor/Services/IThemeProvider.cs +++ b/src/BootstrapBlazor/Services/IThemeProvider.cs @@ -6,18 +6,25 @@ namespace BootstrapBlazor.Components; /// -/// 主题提供器接口 +/// Interface for theme provider /// public interface IThemeProvider { /// - /// 设置主题方法 + /// Sets the theme asynchronously. /// - /// + /// The name of the theme to set. + /// A representing the asynchronous operation. ValueTask SetThemeAsync(string themeName); /// - /// 获得当前主题方法 + /// Gets the current theme asynchronously. /// + /// A representing the asynchronous operation, with the current theme name as the result. ValueTask GetThemeAsync(); + + /// + /// The callback when theme changed + /// + Func? ThemeChangedAsync { get; set; } } diff --git a/test/UnitTest/Services/ThemeProviderTest.cs b/test/UnitTest/Services/ThemeProviderTest.cs index 3b219a2dae5..0f09ed404db 100644 --- a/test/UnitTest/Services/ThemeProviderTest.cs +++ b/test/UnitTest/Services/ThemeProviderTest.cs @@ -10,8 +10,15 @@ public class ThemeProviderTest : BootstrapBlazorTestBase [Fact] public async Task SetTheme_Ok() { + var themeName = ""; var themeProviderService = Context.Services.GetRequiredService(); + themeProviderService.ThemeChangedAsync = async theme => + { + themeName = theme; + await Task.CompletedTask; + }; await themeProviderService.SetThemeAsync("light"); + Assert.Equal("light", themeName); } [Fact]