迁移到 Ant Design 并优化 DNS 接口

将项目从 Fluent UI 迁移到 Ant Design Blazor:
- 替换 UI 组件,更新样式和脚本。
- 移除 Fluent UI 相关依赖,添加 Ant Design 依赖。

优化 DNS 解析接口:
- 将解析接口从 POST 改为 GET,简化参数结构。
- 更新 `DnsResponse` 和配置文件结构。

数据库模型优化:
- 为 JSON 数据存储添加 `JArray` 映射支持。

初始化逻辑重构:
- 简化 `AutoInit` 初始化逻辑,提升代码可读性。

其他改动:
- 升级依赖包版本。
- 移除无用命名空间和属性。
This commit is contained in:
Megghy
2025-09-25 17:15:57 +08:00
parent 5863336e20
commit b2c648c3e6
14 changed files with 256 additions and 324 deletions

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-cn"> <html lang="zh-cn">
<head> <head>
@@ -7,17 +7,17 @@
<base href="/" /> <base href="/" />
<link rel="stylesheet" href="@Assets["app.css"]" /> <link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["MegghysAPI.styles.css"]" /> <link rel="stylesheet" href="@Assets["MegghysAPI.styles.css"]" />
<link rel="stylesheet" href="_content/AntDesign/css/ant-design-blazor.css" />
<ImportMap /> <ImportMap />
<link rel="icon" type="image/x-icon" href="favicon.ico" /> <link rel="icon" type="image/x-icon" href="favicon.ico" />
<HeadOutlet /> <HeadOutlet />
<script src="_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js" type="text/javascript"></script>
<loading-theme storage-name="theme"></loading-theme>
</head> </head>
<body> <body>
<ConfigProvider>
<Routes /> <Routes />
<FluentDesignTheme StorageName="theme" /> </ConfigProvider>
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
<script src="_framework/blazor.web.js"></script> <script src="_framework/blazor.web.js"></script>
</body> </body>

View File

@@ -1,26 +1,43 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
<FluentLayout> <Layout class="app-layout">
<FluentHeader> <Header class="app-header">
MegghysAPI <div class="app-header-title">MegghysAPI</div>
</FluentHeader> </Header>
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%"> <Layout>
<NavMenu /> <Sider Collapsible="true"
<FluentBodyContent Class="body-content"> Collapsed="@collapsed"
OnCollapse="HandleCollapse"
Width="220"
Class="app-sider">
<NavMenu InlineCollapsed="@collapsed" />
</Sider>
<Layout>
<Content class="app-content">
<div class="content"> <div class="content">
@Body @Body
</div> </div>
</FluentBodyContent> </Content>
</FluentStack> <Footer class="app-footer">
<FluentFooter> <a href="https://antblazor.com" target="_blank">Ant Design Blazor 文档</a>
<a href="https://www.fluentui-blazor.net" target="_blank">Documentation and demos</a> <span class="footer-spacer"></span>
<FluentSpacer /> <a href="https://learn.microsoft.com/aspnet/core/blazor" target="_blank">About Blazor</a>
<a href="https://learn.microsoft.com/en-us/aspnet/core/blazor" target="_blank">About Blazor</a> </Footer>
</FluentFooter> </Layout>
</FluentLayout> </Layout>
</Layout>
<div id="blazor-error-ui" data-nosnippet> <div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred. An unhandled error has occurred.
<a href="." class="reload">Reload</a> <a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span> <span class="dismiss">🗙</span>
</div> </div>
@code {
private bool collapsed;
private void HandleCollapse(bool isCollapsed)
{
collapsed = isCollapsed;
}
}

View File

@@ -1,20 +1,80 @@
@rendermode InteractiveServer @rendermode InteractiveServer
@implements IDisposable
<div class="navmenu"> <Menu Mode="MenuMode.Inline"
<input type="checkbox" title="Menu expand/collapse toggle" id="navmenu-toggle" class="navmenu-icon" /> Theme="MenuTheme.Dark"
<label for="navmenu-toggle" class="navmenu-icon"><FluentIcon Value="@(new Icons.Regular.Size20.Navigation())" Color="Color.Fill" /></label> InlineCollapsed="@InlineCollapsed"
<nav class="sitenav" aria-labelledby="main-menu"> SelectedKeys="@selectedKeys"
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="expanded" CustomToggle="true"> OnMenuItemClicked="HandleMenuClick"
<FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink> Class="app-menu">
<FluentNavLink Href="pixiv" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Pixiv</FluentNavLink> <MenuItem Key="/">
<FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink> <Icon Type="IconType.OutlineHome" />
</FluentNavMenu> <span>Home</span>
</nav> </MenuItem>
<FluentDesignTheme @bind-Mode="@Mode" @bind-OfficeColor="@OfficeColor" StorageName="theme" /> <MenuItem Key="pixiv">
</div> <Icon Type="IconType.OutlinePicture" />
<span>Pixiv</span>
</MenuItem>
<MenuItem Key="weather">
<Icon Type="IconType.OutlineCloud" />
<span>Weather</span>
</MenuItem>
</Menu>
@code { @code {
private bool expanded = true; [Parameter]
public DesignThemeModes Mode { get; set; } public bool InlineCollapsed { get; set; }
public OfficeColor? OfficeColor { get; set; }
private string[] selectedKeys = ["/"];
[Inject]
private NavigationManager NavigationManager { get; set; } = default!;
protected override void OnParametersSet()
{
UpdateSelectedKeys(NavigationManager.Uri);
}
protected override void OnInitialized()
{
UpdateSelectedKeys(NavigationManager.Uri);
NavigationManager.LocationChanged += HandleLocationChanged;
}
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
{
UpdateSelectedKeys(e.Location);
StateHasChanged();
}
private void UpdateSelectedKeys(string uri)
{
var relative = NavigationManager.ToBaseRelativePath(uri);
if (string.IsNullOrEmpty(relative))
{
selectedKeys = ["/"];
}
else
{
var key = relative.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? string.Empty;
selectedKeys = [key];
}
}
private void HandleMenuClick(MenuItem menuItem)
{
if (menuItem.Key is null)
{
return;
}
selectedKeys = [menuItem.Key];
var target = menuItem.Key == "/" ? "/" : $"/{menuItem.Key}";
NavigationManager.NavigateTo(target);
}
public void Dispose()
{
NavigationManager.LocationChanged -= HandleLocationChanged;
}
} }

View File

@@ -1,21 +0,0 @@
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<div role="status" style="padding-bottom: 1em;">
Current count: <FluentBadge Appearance="Appearance.Neutral">@currentCount</FluentBadge>
</div>
<FluentButton Appearance="Appearance.Accent" @onclick="IncrementCount">Click me</FluentButton>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@@ -1,14 +1,13 @@
@page "/pixiv" @page "/pixiv"
@rendermode InteractiveServer @rendermode InteractiveServer
<h3>Pixiv</h3> <h3>Pixiv</h3>
<FluentButton OnClick="RandomGet"> <Space Direction="SpaceDirection.Horizontal" Size="@("16")" Align="SpaceAlign.Center">
随机获取 <Button Type="ButtonType.Primary" OnClick="RandomGet">随机获取</Button>
</FluentButton> </Space>
<FluentDivider /> <Divider />
<br/> <Space Direction="SpaceDirection.Vertical" Size="@("16")" Align="SpaceAlign.Center">
<FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Center">
@foreach (var (index, img) in CurrentImgs.S3URL.Index()) @foreach (var (index, img) in CurrentImgs.S3URL.Index())
{ {
@if(index == 0) @if(index == 0)
@@ -20,7 +19,7 @@
<img src="@img.Large" loading="lazy" referrerpolicy="no-referrer" /> <img src="@img.Large" loading="lazy" referrerpolicy="no-referrer" />
} }
} }
</FluentStack> </Space>
@code { @code {
public Modules.PixivFavoriteDownloader.Pixiv.PixivImgInfo CurrentImgs; public Modules.PixivFavoriteDownloader.Pixiv.PixivImgInfo CurrentImgs;

View File

@@ -1,43 +0,0 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
<!-- This page is rendered in SSR mode, so the FluentDataGrid component does not offer any interactivity (like sorting). -->
<FluentDataGrid Id="weathergrid" Items="@forecasts" GridTemplateColumns="1fr 1fr 1fr 2fr" Loading="@(forecasts == null)" Style="height:204px;" TGridItem="WeatherForecast">
<PropertyColumn Title="Date" Property="@(c => c!.Date)" Align="Align.Start"/>
<PropertyColumn Title="Temp. (C)" Property="@(c => c!.TemperatureC)" Align="Align.Center"/>
<PropertyColumn Title="Temp. (F)" Property="@(c => c!.TemperatureF)" Align="Align.Center"/>
<PropertyColumn Title="Summary" Property="@(c => c!.Summary)" Align="Align.End"/>
</FluentDataGrid>
@code {
private IQueryable<WeatherForecast>? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).AsQueryable();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View File

@@ -1,12 +1,11 @@
@using System.Net.Http @using System.Net.Http
@using System.Net.Http.Json @using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode @using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.FluentUI.AspNetCore.Components @using AntDesign
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using MegghysAPI @using MegghysAPI
@using MegghysAPI.Components @using MegghysAPI.Components

View File

@@ -23,6 +23,11 @@ namespace MegghysAPI.Controllers
public class DnsResponse public class DnsResponse
{ {
public class DnsQuestion
{
public string Name { get; set; }
public int type { get; set; }
}
[JsonPropertyName("Status")] [JsonPropertyName("Status")]
public int Status { get; set; } public int Status { get; set; }
@@ -42,17 +47,12 @@ namespace MegghysAPI.Controllers
public bool Cd { get; set; } public bool Cd { get; set; }
[JsonPropertyName("Question")] [JsonPropertyName("Question")]
public List<Dictionary<string, object>> Question { get; set; } = new(); public DnsQuestion Question { get; set; }
[JsonPropertyName("Answer")] [JsonPropertyName("Answer")]
public List<DnsRecord>? Answer { get; set; } public List<DnsRecord>? Answer { get; set; }
} }
public class DnsRequest
{
public string Domain { get; set; } = string.Empty;
public string RecordType { get; set; } = "A";
}
[Route("api/public")] [Route("api/public")]
[ApiController] [ApiController]
public class PublicController : MControllerBase public class PublicController : MControllerBase
@@ -67,8 +67,8 @@ namespace MegghysAPI.Controllers
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower(); return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
} }
[HttpPost("resolve-dns")] [HttpGet("resolve-dns")]
public static async Task<IActionResult> ResolveDns([FromBody] DnsRequest request) public async Task<IActionResult> ResolveDns([FromQuery] string domain, [FromQuery] string recordType = "A")
{ {
try try
{ {
@@ -84,9 +84,9 @@ namespace MegghysAPI.Controllers
} }
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var key = CalculateKey(accountId, akSecret, timestamp, request.Domain, akId); var key = CalculateKey(accountId, akSecret, timestamp, domain, akId);
var url = $"{endpoint}?name={Uri.EscapeDataString(request.Domain)}&type={request.RecordType}&uid={accountId}&ak={akId}&key={key}&ts={timestamp}"; var url = $"{endpoint}?name={Uri.EscapeDataString(domain)}&type={recordType}&uid={accountId}&ak={akId}&key={key}&ts={timestamp}";
Logs.Info($"正在向 {url} 发起DNS请求"); Logs.Info($"正在向 {url} 发起DNS请求");

1
DB.cs
View File

@@ -6,7 +6,6 @@ namespace MegghysAPI
public static class DB public static class DB
{ {
public static IFreeSql SQL { get; private set; } public static IFreeSql SQL { get; private set; }
[AutoInit(Order = 0)]
internal static void InitDB() internal static void InitDB()
{ {
SQL = new FreeSqlBuilder() SQL = new FreeSqlBuilder()

View File

@@ -8,10 +8,8 @@
"MinIOSecretKey": "Nko5azOSUiYgOUeLsj8hLxGz4cKC8XOcH0VS7lWq", "MinIOSecretKey": "Nko5azOSUiYgOUeLsj8hLxGz4cKC8XOcH0VS7lWq",
"MinIORegion": "cn-main", "MinIORegion": "cn-main",
"MinIOBucket": "general", "MinIOBucket": "general",
"DnsResolver": { "DnsResolverAccountId": "720341",
"AccountId": "720341", "DnsResolverAkId": "720341_30798028364391424",
"AkId": "720341_30798028364391424", "DnsResolverAkSecret": "0739b7584ab54c1b9585f10594af0cd0",
"AkSecret": "0739b7584ab54c1b9585f10594af0cd0", "DnsResolverEndpoint": "https://223.5.5.5/resolve"
"Endpoint": "https://223.5.5.5/resolve"
}
} }

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
@@ -7,12 +7,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FreeSql.Extensions.JsonMap" Version="3.5.106" /> <PackageReference Include="FreeSql.Extensions.JsonMap" Version="3.5.213" />
<PackageReference Include="FreeSql.Provider.PostgreSQL" Version="3.5.106" /> <PackageReference Include="FreeSql.Provider.PostgreSQL" Version="3.5.213" />
<PackageReference Include="Masuit.Tools.Core" Version="2025.1.4" /> <PackageReference Include="Masuit.Tools.Core" Version="2025.5.1" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.11.5" /> <PackageReference Include="Minio" Version="6.0.5" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.11.5" /> <PackageReference Include="AntDesign" Version="1.4.3" />
<PackageReference Include="Minio" Version="6.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -2,6 +2,7 @@
using System.Web; using System.Web;
using FreeSql.DataAnnotations; using FreeSql.DataAnnotations;
using MegghysAPI.Attributes; using MegghysAPI.Attributes;
using Newtonsoft.Json.Linq;
using static MegghysAPI.Modules.PixivFavoriteDownloader.Pixiv; using static MegghysAPI.Modules.PixivFavoriteDownloader.Pixiv;
namespace MegghysAPI.Modules namespace MegghysAPI.Modules
@@ -184,19 +185,18 @@ namespace MegghysAPI.Modules
public long Id { get; set; } public long Id { get; set; }
public PixivImgType Type { get; set; } public PixivImgType Type { get; set; }
public string Title { get; set; } public string Title { get; set; }
[JsonMap]
public PixivRestrictInfo Restrict { get; set; } public PixivRestrictInfo Restrict { get; set; }
[Column(DbType = "text")] [Column(DbType = "text")]
public string Description { get; set; } public string Description { get; set; }
[JsonMap] [JsonMap]
public PixivAuthorInfo Author { get; set; } public PixivAuthorInfo Author { get; set; }
public bool R18 => Restrict != PixivRestrictInfo.Normal; public bool R18 => Restrict != PixivRestrictInfo.Normal;
[JsonMap] [JsonMap, Column(MapType = typeof(JArray))]
public List<PixivTagInfo> Tags { get; set; } = []; public List<PixivTagInfo> Tags { get; set; } = [];
[JsonMap] [JsonMap, Column(MapType = typeof(JArray))]
public List<PixivURLInfo> URL { get; set; } = []; public List<PixivURLInfo> URL { get; set; } = [];
[JsonMap] [JsonMap, Column(MapType = typeof(JArray))]
public List<PixivURLInfo> S3URL { get; set; } = []; public List<PixivURLInfo> S3URL { get; set; } = [];
public int Width { get; set; } public int Width { get; set; }
public int Height { get; set; } public int Height { get; set; }

View File

@@ -2,14 +2,14 @@ using System.Diagnostics;
using System.Reflection; using System.Reflection;
using MegghysAPI.Attributes; using MegghysAPI.Attributes;
using MegghysAPI.Components; using MegghysAPI.Components;
using Microsoft.FluentUI.AspNetCore.Components; using AntDesign;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
builder.Services.AddRazorComponents() builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(); .AddInteractiveServerComponents();
builder.Services.AddFluentUIComponents(); builder.Services.AddAntDesign();
builder.Services.AddControllers(); builder.Services.AddControllers();
var app = builder.Build(); var app = builder.Build();
@@ -36,36 +36,48 @@ app.UseAntiforgery();
app.MapControllers(); app.MapControllers();
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> AutoInitAttribute <EFBFBD><EFBFBD><EFBFBD><EFBFBD> // 加载所有有 AutoInitAttribute 的类
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰAssembly // 获取当前Assembly
var inits = new List<MethodInfo>(); var inits = new List<MethodInfo>();
Assembly.GetExecutingAssembly() foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
.GetTypes()
.ForEach(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
.Where(m => m.GetCustomAttribute<AutoInitAttribute>() is { }).ForEach(m => inits.Add(m)));
inits = [.. inits.OrderBy(m => m.GetCustomAttribute<AutoInitAttribute>().Order)];
inits.ForEach(m =>
{ {
var sw = new Stopwatch(); foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
sw.Start(); .Where(m => m.GetCustomAttribute<AutoInitAttribute>() is not null))
var attr = m.GetCustomAttribute<AutoInitAttribute>();
if (attr.LogMessage is not null)
Logs.Info(attr.LogMessage);
if (attr.Async)
{ {
Task.Run(() => inits.Add(method);
}
}
DB.InitDB();
inits = inits
.OrderBy(m => m.GetCustomAttribute<AutoInitAttribute>()?.Order ?? 0)
.ToList();
foreach (var method in inits)
{
var sw = Stopwatch.StartNew();
var attr = method.GetCustomAttribute<AutoInitAttribute>();
if (attr?.LogMessage is { } message)
{ {
m.Invoke(null, null); Logs.Info(message);
}
if (attr?.Async is true)
{
_ = Task.Run(() =>
{
method.Invoke(null, null);
attr.PostInit?.Invoke(); attr.PostInit?.Invoke();
Logs.Info($"[{sw.ElapsedMilliseconds} ms] Async <{m.DeclaringType.Name}.{m.Name}> => Inited."); Logs.Info($"[{sw.ElapsedMilliseconds} ms] Async <{method.DeclaringType?.Name}.{method.Name}> => Inited.");
}); });
} }
else else
{ {
m.Invoke(null, null); method.Invoke(null, null);
attr.PostInit?.Invoke(); attr?.PostInit?.Invoke();
Logs.Info($"[{sw.ElapsedMilliseconds} ms] <{m.DeclaringType.Name}.{m.Name}> => Inited."); Logs.Info($"[{sw.ElapsedMilliseconds} ms] <{method.DeclaringType?.Name}.{method.Name}> => Inited.");
} }
}); }
app.Run(); app.Run();

View File

@@ -1,80 +1,71 @@
@import '/_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css';
body {
--body-font: "Segoe UI Variable", "Segoe UI", sans-serif;
font-family: var(--body-font);
font-size: var(--type-ramp-base-font-size);
line-height: var(--type-ramp-base-line-height);
margin: 0;
}
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100vh; min-height: 100vh;
font-family: var(--body-font); font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
font-size: var(--type-ramp-base-font-size); background: #f0f2f5;
line-height: var(--type-ramp-base-line-height); color: rgba(0, 0, 0, 0.85);
font-weight: var(--font-weight);
color: var(--neutral-foreground-rest);
background: var(--neutral-fill-layer-rest);
} }
.navmenu-icon { .app-layout {
display: none; min-height: 100vh;
} }
.main { .app-header {
min-height: calc(100dvh - 86px); background: #001529;
color: var(--neutral-foreground-rest);
align-items: stretch !important;
}
.body-content {
align-self: stretch;
height: calc(100dvh - 86px) !important;
display: flex; display: flex;
align-items: center;
padding: 0 24px;
height: 64px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.app-header-title {
color: #fff;
font-size: 20px;
font-weight: 600;
}
.app-sider {
background: #001529;
min-height: calc(100vh - 64px);
}
.app-menu {
height: 100%;
border-inline-end: none !important;
}
.app-content {
padding: 24px;
background: #fff;
min-height: calc(100vh - 64px - 70px);
} }
.content { .content {
padding: 0.5rem 1.5rem; min-height: 100%;
align-self: stretch !important;
width: 100%;
} }
.manage { .app-footer {
width: 100dvw; display: flex;
}
footer {
background: var(--neutral-layer-4);
color: var(--neutral-foreground-rest);
align-items: center; align-items: center;
padding: 10px 10px; padding: 16px 24px;
background: #fff;
border-top: 1px solid #f0f0f0;
gap: 12px;
} }
footer a { .footer-spacer {
color: var(--neutral-foreground-rest); flex: 1;
text-decoration: none;
}
footer a:focus {
outline: 1px dashed;
outline-offset: 3px;
}
footer a:hover {
text-decoration: underline;
}
.alert {
border: 1px dashed var(--accent-fill-rest);
padding: 5px;
} }
.weather-table {
background: #fff;
}
#blazor-error-ui { #blazor-error-ui {
background: lightyellow; background: #fffbe6;
border: 1px solid #ffe58f;
bottom: 0; bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none; display: none;
@@ -83,121 +74,43 @@ footer {
position: fixed; position: fixed;
width: 100%; width: 100%;
z-index: 1000; z-index: 1000;
margin: 20px 0;
} }
#blazor-error-ui .dismiss { #blazor-error-ui .dismiss {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
right: 0.75rem; right: 0.75rem;
top: 0.5rem; top: 0.5rem;
} }
.blazor-error-boundary { .blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; border: 1px solid #ff4d4f;
padding: 1rem 1rem 1rem 3.7rem; padding: 16px 24px;
color: white; border-radius: 4px;
background: #fff2f0;
color: #a8071a;
} }
.blazor-error-boundary::before { .blazor-error-boundary::before {
content: "An error has occurred. " content: "An error has occurred. ";
} font-weight: 600;
.loading-progress {
position: relative;
display: block;
width: 8rem;
height: 8rem;
margin: 20vh auto 1rem auto;
} }
.loading-progress circle {
fill: none;
stroke: #e0e0e0;
stroke-width: 0.6rem;
transform-origin: 50% 50%;
transform: rotate(-90deg);
}
.loading-progress circle:last-child {
stroke: #1b6ec2;
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
position: absolute;
text-align: center;
font-weight: bold;
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}
.loading-progress-text:after {
content: var(--blazor-load-percentage-text, "Loading");
}
code { code {
color: #c02d76; color: #c02d76;
} }
@media (max-width: 600px) { @media (max-width: 768px) {
.header-gutters { .app-content {
margin: 0.5rem 3rem 0.5rem 1.5rem !important; padding: 16px;
} }
[dir="rtl"] .header-gutters { .app-footer {
margin: 0.5rem 1.5rem 0.5rem 3rem !important; flex-direction: column;
align-items: flex-start;
} }
.main { .footer-spacer {
flex-direction: column !important;
row-gap: 0 !important;
}
nav.sitenav {
width: 100%;
height: 100%;
}
#main-menu {
width: 100% !important;
}
#main-menu > div:first-child:is(.expander) {
display: none; display: none;
} }
.navmenu {
width: 100%;
}
#navmenu-toggle {
appearance: none;
}
#navmenu-toggle ~ nav {
display: none;
}
#navmenu-toggle:checked ~ nav {
display: block;
}
.navmenu-icon {
cursor: pointer;
z-index: 10;
display: block;
position: absolute;
top: 15px;
left: unset;
right: 20px;
width: 20px;
height: 20px;
border: none;
}
[dir="rtl"] .navmenu-icon {
left: 20px;
right: unset;
}
} }