Skip to content

Commit 50186e5

Browse files
j4587698ArgoZhang
andauthored
feat(ThemeProvider): support auto mode (#4400)
* 更改Theme切换的实现方式 * 删除部分调试信息 * refactor: 重构代码 * refactor: 代码重构提高可读性 * refactor: 代码重构 * refactor: 精简代码 * test: 更新单元测试 * refactor: 更新代码 * refactor: 保持动画效果一致 * refactor: 增加保护逻辑 * refactor: 提高代码可读性 --------- Co-authored-by: Argo-AscioTech <[email protected]>
1 parent d0d24b3 commit 50186e5

File tree

9 files changed

+134
-71
lines changed

9 files changed

+134
-71
lines changed

src/BootstrapBlazor.Server/Components/App.razor

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
<Link Href="_content/BootstrapBlazor/css/motronic.min.css" />
2424
<Link Href="BootstrapBlazor.Server.styles.css" />
2525
<Link Href="css/site.css" />
26-
<ThemeLoader></ThemeLoader>
2726
<HeadOutlet @rendermode="new InteractiveServerRenderMode(false)" />
2827
<title>@Localizer["Title"]</title>
2928
</head>

src/BootstrapBlazor.Server/Components/Components/Header.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@ protected override void OnInitialized()
5757
_versionString = $"v{PackageVersionService.Version}";
5858
}
5959

60-
private Task OnThemeChangedAsync(string themeName) => InvokeVoidAsync("updateTheme", themeName);
60+
private Task OnThemeChangedAsync(ThemeValue themeName) => InvokeVoidAsync("updateTheme", themeName);
6161
}

src/BootstrapBlazor.Server/Controllers/Api/LoginController.cs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,18 @@
99
namespace BootstrapBlazor.Server.Controllers.Api;
1010

1111
/// <summary>
12-
///
12+
/// 登录控制器
1313
/// </summary>
1414
[Route("api/[controller]")]
1515
[AllowAnonymous]
1616
[ApiController]
1717
public class LoginController : ControllerBase
1818
{
1919
/// <summary>
20-
///
20+
/// 认证方法
2121
/// </summary>
2222
/// <param name="user"></param>
2323
/// <returns></returns>
2424
[HttpPost]
25-
public IActionResult Post(User user)
26-
{
27-
IActionResult? response;
28-
if (user.UserName == "admin" && user.Password == "123456")
29-
{
30-
response = new JsonResult(new { Code = 200, Message = "登录成功" });
31-
}
32-
else
33-
{
34-
response = new JsonResult(new { Code = 500, Message = "用户名或密码错误" });
35-
}
36-
return response;
37-
}
25+
public IActionResult Post(User user) => user is { UserName: "admin", Password: "123456" } ? new JsonResult(new { Code = 200, Message = "登录成功" }) : new JsonResult(new { Code = 500, Message = "用户名或密码错误" });
3826
}

src/BootstrapBlazor/Components/ThemeProvider/ThemeProvider.razor.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,19 @@ public partial class ThemeProvider
6969
/// 获得/设置 主题切换回调方法
7070
/// </summary>
7171
[Parameter]
72-
public Func<string, Task>? OnThemeChangedAsync { get; set; }
72+
public Func<ThemeValue, Task>? OnThemeChangedAsync { get; set; }
73+
74+
/// <summary>
75+
/// 主题类型
76+
/// </summary>
77+
[Parameter]
78+
public ThemeValue ThemeValue { get; set; } = ThemeValue.UseLocalStorage;
79+
80+
/// <summary>
81+
/// 主题类型改变回调方法
82+
/// </summary>
83+
[Parameter]
84+
public EventCallback<ThemeValue> ThemeValueChanged { get; set; }
7385

7486
[Inject, NotNull]
7587
private IIconTheme? IconTheme { get; set; }
@@ -107,16 +119,21 @@ protected override void OnParametersSet()
107119
/// <inheritdoc/>
108120
/// </summary>
109121
/// <returns></returns>
110-
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, OnThemeChangedAsync != null ? nameof(OnThemeChanged) : null);
122+
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, ThemeValue, nameof(OnThemeChanged));
111123

112124
/// <summary>
113125
/// JavaScript 回调方法
114126
/// </summary>
115127
/// <param name="name"></param>
116128
/// <returns></returns>
117129
[JSInvokable]
118-
public async Task OnThemeChanged(string name)
130+
public async Task OnThemeChanged(ThemeValue name)
119131
{
132+
if (ThemeValueChanged.HasDelegate)
133+
{
134+
await ThemeValueChanged.InvokeAsync(name);
135+
}
136+
120137
if (OnThemeChangedAsync != null)
121138
{
122139
await OnThemeChangedAsync(name);
Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,56 @@
1-
import { getAutoThemeValue, getPreferredTheme, setActiveTheme, switchTheme } from "../../modules/utility.js"
1+
import { getPreferredTheme, setTheme, switchTheme } from "../../modules/utility.js"
22
import EventHandler from "../../modules/event-handler.js"
3+
import Data from "../../modules/data.js"
34

4-
export function init(id, invoke, callback) {
5+
export function init(id, invoke, themeValue, callback) {
56
const el = document.getElementById(id);
6-
if (el) {
7-
const currentTheme = getPreferredTheme();
8-
const activeItem = el.querySelector(`.dropdown-item[data-bb-theme-value="${currentTheme}"]`);
9-
if (activeItem) {
10-
setActiveTheme(el, activeItem);
11-
}
7+
if (el === null) {
8+
return;
9+
}
10+
11+
const theme = { el };
12+
Data.set(id, theme);
13+
14+
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
15+
EventHandler.on(darkModeMediaQuery, 'change', () => changeTheme(id));
16+
theme.mediaQueryList = darkModeMediaQuery;
1217

13-
const items = el.querySelectorAll('.dropdown-item');
14-
items.forEach(item => {
15-
EventHandler.on(item, 'click', () => {
16-
setActiveTheme(el, item);
17-
18-
let theme = item.getAttribute('data-bb-theme-value');
19-
if (theme === 'auto') {
20-
theme = getAutoThemeValue();
21-
}
22-
switchTheme(theme, window.innerWidth, 0);
23-
if (callback) {
24-
invoke.invokeMethodAsync(callback, theme);
25-
}
26-
});
27-
});
18+
let currentTheme = themeValue;
19+
if (currentTheme === 'useLocalStorage') {
20+
currentTheme = getPreferredTheme();
2821
}
22+
setTheme(currentTheme, true);
23+
theme.currentTheme = currentTheme;
24+
25+
EventHandler.on(el, 'click', '.dropdown-item', e => {
26+
const activeTheme = e.delegateTarget.getAttribute('data-bb-theme-value');
27+
theme.currentTheme = activeTheme;
28+
switchTheme(activeTheme, window.innerWidth, 0);
29+
if (callback) {
30+
invoke.invokeMethodAsync(callback, activeTheme);
31+
}
32+
});
2933
}
3034

3135
export function dispose(id) {
32-
const el = document.getElementById(id);
33-
const items = el.querySelectorAll('.dropdown-item');
34-
items.forEach(item => {
35-
EventHandler.off(item, 'click');
36-
});
36+
const theme = Data.get(id);
37+
if (theme === null) {
38+
return;
39+
}
40+
Data.remove(id);
41+
42+
const { el, darkModeMediaQuery } = theme;
43+
EventHandler.off(el, 'click');
44+
EventHandler.off(darkModeMediaQuery, 'change');
45+
}
46+
47+
const changeTheme = id => {
48+
const theme = Data.get(id);
49+
if (theme === null) {
50+
return;
51+
}
52+
53+
if (theme.currentTheme === 'auto') {
54+
switchTheme('auto', window.innerWidth, 0);
55+
}
3756
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) Argo Zhang ([email protected]). All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
// Website: https://www.blazor.zone or https://argozhang.github.io/
4+
5+
using BootstrapBlazor.Core.Converter;
6+
7+
namespace BootstrapBlazor.Components;
8+
9+
/// <summary>
10+
/// 主题选项
11+
/// </summary>
12+
[JsonEnumConverter(true)]
13+
public enum ThemeValue
14+
{
15+
/// <summary>
16+
/// 自动
17+
/// </summary>
18+
Auto,
19+
20+
/// <summary>
21+
/// 明亮主题
22+
/// </summary>
23+
Light,
24+
25+
/// <summary>
26+
/// 暗黑主题
27+
/// </summary>
28+
Dark,
29+
30+
/// <summary>
31+
/// 使用本地保存选项
32+
/// </summary>
33+
UseLocalStorage,
34+
}

src/BootstrapBlazor/Converter/JsonEnumConverter.cs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,26 @@ namespace BootstrapBlazor.Core.Converter;
1212
/// </summary>
1313
public class JsonEnumConverter : JsonConverterAttribute
1414
{
15-
/// <summary>
16-
/// 构造函数
17-
/// </summary>
18-
public JsonEnumConverter() : base()
19-
{
20-
21-
}
22-
2315
/// <summary>
2416
/// 构造函数
2517
/// </summary>
2618
/// <param name="camelCase">Optional naming policy for writing enum values.</param>
2719
/// <param name="allowIntegerValues">True to allow undefined enum values. When true, if an enum value isn't defined it will output as a number rather than a string.</param>
28-
public JsonEnumConverter(bool camelCase, bool allowIntegerValues = true) : this()
20+
public JsonEnumConverter(bool camelCase = false, bool allowIntegerValues = true)
2921
{
30-
CamelCase = camelCase;
31-
AllowIntegerValues = allowIntegerValues;
22+
_camelCase = camelCase;
23+
_allowIntegerValues = allowIntegerValues;
3224
}
3325

3426
/// <summary>
3527
/// naming policy for writing enum values
3628
/// </summary>
37-
public bool CamelCase { get; private set; }
29+
private readonly bool _camelCase;
3830

3931
/// <summary>
4032
/// True to allow undefined enum values. When true, if an enum value isn't defined it will output as a number rather than a string
4133
/// </summary>
42-
public bool AllowIntegerValues { get; private set; } = true;
34+
private readonly bool _allowIntegerValues;
4335

4436
/// <summary>
4537
/// <inheritdoc/>
@@ -48,9 +40,9 @@ public JsonEnumConverter(bool camelCase, bool allowIntegerValues = true) : this(
4840
/// <returns></returns>
4941
public override JsonConverter? CreateConverter(Type typeToConvert)
5042
{
51-
var converter = CamelCase
52-
? new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, AllowIntegerValues)
53-
: new JsonStringEnumConverter(null, AllowIntegerValues);
43+
var converter = _camelCase
44+
? new JsonStringEnumConverter(JsonNamingPolicy.CamelCase, _allowIntegerValues)
45+
: new JsonStringEnumConverter(null, _allowIntegerValues);
5446
return converter;
5547
}
5648
}

src/BootstrapBlazor/wwwroot/modules/utility.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,9 @@ export function getTheme() {
716716
}
717717

718718
export function saveTheme(theme) {
719-
localStorage.setItem('theme', theme)
719+
if (localStorage) {
720+
localStorage.setItem('theme', theme);
721+
}
720722
}
721723

722724
export function getAutoThemeValue() {

test/UnitTest/Components/ThemeProviderTest.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,29 @@ public void ThemeProvider_Ok()
2121
[Fact]
2222
public async Task OnThemeChanged_Ok()
2323
{
24-
var name = "";
24+
var v = ThemeValue.Auto;
2525
var cut = Context.RenderComponent<ThemeProvider>(pb =>
2626
{
27-
pb.Add(a => a.OnThemeChangedAsync, t =>
27+
pb.Add(a => a.OnThemeChangedAsync, val =>
2828
{
29-
name = t;
29+
v = val;
3030
return Task.CompletedTask;
3131
});
32-
pb.Add(a => a.Alignment, Alignment.Center);
3332
});
34-
await cut.Instance.OnThemeChanged("dark");
35-
Assert.Equal("dark", name);
33+
await cut.Instance.OnThemeChanged(ThemeValue.Dark);
34+
Assert.Equal(ThemeValue.Dark, v);
35+
}
36+
37+
[Fact]
38+
public async Task ThemeValueChanged_Ok()
39+
{
40+
var v = ThemeValue.Auto;
41+
var cut = Context.RenderComponent<ThemeProvider>(pb =>
42+
{
43+
pb.Add(a => a.ThemeValue, ThemeValue.Light);
44+
pb.Add(a => a.ThemeValueChanged, EventCallback.Factory.Create<ThemeValue>(this, val => v = val));
45+
});
46+
await cut.Instance.OnThemeChanged(ThemeValue.Dark);
47+
Assert.Equal(ThemeValue.Dark, v);
3648
}
3749
}

0 commit comments

Comments
 (0)