docs: add blazor webapp hosting project (#3642)

* 测试上传

* 有js报错

* remove ConventionRouter

* fix assets and js interop

---------

Co-authored-by: James Yeung <shunjiey@hotmail.com>
This commit is contained in:
Zhu Lijun 2024-02-04 23:04:55 +08:00 committed by GitHub
parent 3f9443b229
commit f893456a6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 415 additions and 677 deletions

View File

@ -45,6 +45,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.TestApp.Server",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AntDesign.TestApp.Client", "tests\AntDesign.TestApp\Client\AntDesign.TestApp.Client.csproj", "{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntDesign.Docs.WebApp", "site\AntDesign.Docs.WebApp\AntDesign.Docs.WebApp\AntDesign.Docs.WebApp.csproj", "{B888A244-7049-4065-937B-9881C45BA0DB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AntDesign.Docs.WebApp.Client", "site\AntDesign.Docs.WebApp\AntDesign.Docs.WebApp.Client\AntDesign.Docs.WebApp.Client.csproj", "{8572A4DA-1852-4816-A876-D2E1CC96481A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -99,6 +103,14 @@ Global
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5}.Release|Any CPU.Build.0 = Release|Any CPU
{B888A244-7049-4065-937B-9881C45BA0DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B888A244-7049-4065-937B-9881C45BA0DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B888A244-7049-4065-937B-9881C45BA0DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B888A244-7049-4065-937B-9881C45BA0DB}.Release|Any CPU.Build.0 = Release|Any CPU
{8572A4DA-1852-4816-A876-D2E1CC96481A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8572A4DA-1852-4816-A876-D2E1CC96481A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8572A4DA-1852-4816-A876-D2E1CC96481A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8572A4DA-1852-4816-A876-D2E1CC96481A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -117,6 +129,8 @@ Global
{606789E3-AFE9-4489-9963-2B06A701D6B6} = {DFD13180-D1BF-44DA-BEBE-4A54EFDEFFE2}
{32A251DB-FF95-4325-B80F-0E3B0FA5CBC5} = {606789E3-AFE9-4489-9963-2B06A701D6B6}
{5ADDB648-9417-4FE9-8A7D-0D4BF2DD3EC5} = {606789E3-AFE9-4489-9963-2B06A701D6B6}
{B888A244-7049-4065-937B-9881C45BA0DB} = {752F5AE8-BA08-4C41-B9B2-D2ED12727E63}
{8572A4DA-1852-4816-A876-D2E1CC96481A} = {752F5AE8-BA08-4C41-B9B2-D2ED12727E63}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E124DDCB-1F8D-4F96-BF41-D87019D0A412}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\AntDesign.Docs\AntDesign.Docs.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Globalization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using AntDesign;
if (!CultureInfo.CurrentCulture.Name.IsIn("en-US", "zh-CN"))
{
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
}
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddAntDesignDocs();
await builder.Build().RunAsync();

View File

@ -0,0 +1,9 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AntDesign.Docs.WebApp.Client

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AntDesign.Docs.WebApp.Client\AntDesign.Docs.WebApp.Client.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<Watch Include="$(SolutionDir)**\*.razor" />
</ItemGroup>
<ItemGroup>
<DocFiles Include="$(SolutionDir)docs\**\*.*"></DocFiles>
</ItemGroup>
<Target Name="CopyDocs" BeforeTargets="Build">
<Copy SourceFiles="@(DocFiles)" DestinationFolder="$(ProjectDir)\wwwroot\docs\%(RecursiveDir)" ContinueOnError="true" />
</Target>
</Project>

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="icon" href="logo.png" type="image/x-icon" />
<link rel="preconnect" href="https://C9UTAZSOWW-dsn.algolia.net" crossorigin />
<link rel="stylesheet" href="@("https://fastly.jsdelivr.net/npm/@docsearch/css@3")" />
<link href="_content/AntDesign.Docs/css/default.css" rel="stylesheet" />
<link antblazor-css />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="AntDesign.Docs.WebApp.styles.css" />
<HeadOutlet @rendermode="InteractiveAuto" />
</head>
<body>
<app>
<Routes @rendermode="InteractiveAuto" />
</app>
<script src="_content/AntDesign.Charts/g2plot.js"></script>
<script src="_content/AntDesign.Charts/ant-design-charts-blazor.js"></script>
@* <script src="_content/AntDesign/js/ant-design-blazor.js"></script> *@
<script src="_content/AntDesign.Docs/js/prism.js"></script>
<script>
window.XPrism = {};
window.XPrism.highlight = function (code, language) {
return Prism.highlight(code, Prism.languages[language], language);
}
window.XPrism.highlightAll = function () {
Prism.highlightAll();
}
</script>
<script src="@("https://fastly.jsdelivr.net/npm/@docsearch/js@3")"></script>
<script src="_content/AntDesign.Docs/js/docsearch.js"></script>
<script antblazor-js></script>
@* <script src="/blazor.web.js" autostart="false"></script> *@
<script src="/blazor.web.patched.js" autostart="false"></script>
<script>
Blazor.start({
webAssembly: {
loadBootResource: function (type, name, defaultUri, integrity) {
if (type === 'dotnetjs' && name === 'dotnet.js') {
return '/dotnet.patched.js';
}
return `_framework/${name}`;
},
configureRuntime: (builder) => {
builder.withConfig({
maxParallelDownloads: 2, // Adjust this to change the WebAssembly resource throttling amount
});
},
},
});
</script>
</body>
</html>

View File

@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@ -0,0 +1,11 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AntDesign.Docs.WebApp
@using AntDesign.Docs.WebApp.Client
@using AntDesign.Docs.WebApp.Components

View File

@ -0,0 +1,48 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using AntDesign.Docs.WebApp.Components;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddSingleton(sp => new HttpClient()
{
DefaultRequestHeaders =
{
// Use to call the github API on server side
{"User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36 Edg/81.0.416.68"}
}
});
builder.Services.AddAntDesignDocs();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(AntDesign.Docs.WebApp.Client._Imports).Assembly, typeof(AntDesign.Docs.App).Assembly);
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:15888",
"sslPort": 44354
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7193;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,29 @@
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -1,14 +1,2 @@
<ConfigProvider>
<ConventionRouter AppAssembly="@typeof(MainLayout).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<Result Status="404" />
</LayoutView>
</NotFound>
</ConventionRouter>
<AntContainer />
</ConfigProvider>
<Routes />

View File

@ -5,6 +5,12 @@
@page "/experimental/{name}"
@page "/experimental"
@page "/{locale}/components"
@page "/{locale}/components/{name}"
@page "/{locale}/charts/{name}"
@page "/{locale}/charts"
@page "/{locale}/experimental/{name}"
@page "/{locale}/experimental"
<section class="main-container main-container-component">
<article>

View File

@ -13,6 +13,8 @@ namespace AntDesign.Docs.Pages
{
public partial class Components : ComponentBase, IDisposable
{
[Parameter]
public string Locale { get; set; }
[Parameter]
public string Name { get; set; }

View File

@ -1,5 +1,8 @@
@page "/docs/{fileName}"
@page "/docs"
@page "/{locale}/docs/{fileName}"
@page "/{locale}/docs"
<section class="main-container">
<article class="markdown">
@if (_file != null)

View File

@ -15,6 +15,8 @@ namespace AntDesign.Docs.Pages
{
public partial class Docs : ComponentBase, IDisposable
{
[Parameter]
public string Locale { get; set; }
[Parameter] public string FileName { get; set; }
[CascadingParameter]

View File

@ -1,5 +1,8 @@
@layout BlankLayout
@page "/"
@page "/zh-CN/"
@page "/en-US/"
<PageTitle>Ant Design of Blazor</PageTitle>

View File

@ -9,6 +9,8 @@ namespace AntDesign.Docs.Pages
{
public partial class Index : ComponentBase, IDisposable
{
[Parameter]
public string Locale { get; set; }
private Recommend[] _recommends = { };
private Product[] _products = { };

View File

@ -0,0 +1,47 @@
@using System.Reflection
@using AntDesign.Docs.Localization
@using System.Globalization
<ConfigProvider>
<Router AppAssembly="typeof(App).Assembly" OnNavigateAsync="OnNavigateAsync">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
<AntContainer />
</ConfigProvider>
@inject ILanguageService LanguageService;
@inject NavigationManager NavigationManager;
@code{
async Task OnNavigateAsync(NavigationContext navigationContext)
{
var relativeUri = navigationContext.Path;
var currentCulture = LanguageService.CurrentCulture;
var segment = relativeUri.IndexOf('/') > 0 ? relativeUri.Substring(0, relativeUri.IndexOf('/')) : relativeUri;
if (string.IsNullOrWhiteSpace(segment))
{
NavigationManager.NavigateTo($"{currentCulture.Name}/{relativeUri}");
return;
}
else
{
if (segment.IsIn("zh-CN", "en-US"))
{
LanguageService.SetLanguage(CultureInfo.GetCultureInfo(segment));
}
else if (currentCulture.Name.IsIn("zh-CN", "en-US"))
{
NavigationManager.NavigateTo($"{currentCulture.Name}/{relativeUri}");
}
else
{
NavigationManager.NavigateTo($"en-US/{relativeUri}");
return;
}
}
}
}

View File

@ -1,158 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using AntDesign.Core.Extensions;
using AntDesign.Docs.Localization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.JSInterop;
namespace AntDesign.Docs.Routing
{
public class ConventionRouter : IComponent, IHandleAfterRender, IDisposable
{
private RenderHandle _renderHandle;
private bool _navigationInterceptionEnabled;
private string _location;
[Inject] private NavigationManager NavigationManager { get; set; }
[Inject] private INavigationInterception NavigationInterception { get; set; }
[Inject] private RouteManager RouteManager { get; set; }
[Inject] private ILanguageService LanguageService { get; set; }
[Inject] private IJSRuntime JsRuntime { get; set; }
[Parameter] public RenderFragment NotFound { get; set; }
[Parameter] public RenderFragment<RouteData> Found { get; set; }
[Parameter] public Assembly AppAssembly { get; set; }
[Parameter] public string DefaultUrl { get; set; }
public void Attach(RenderHandle renderHandle)
{
_renderHandle = renderHandle;
_location = NavigationManager.Uri;
NavigationManager.LocationChanged += HandleLocationChanged;
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
if (Found == null)
{
throw new InvalidOperationException($"The {nameof(ConventionRouter)} component requires a value for the parameter {nameof(Found)}.");
}
if (NotFound == null)
{
throw new InvalidOperationException($"The {nameof(ConventionRouter)} component requires a value for the parameter {nameof(NotFound)}.");
}
RouteManager.Initialise(AppAssembly);
try
{
Refresh();
}
catch
{
// In the server prerendering mode, it will throw an expection.
}
return Task.CompletedTask;
}
public async Task OnAfterRenderAsync()
{
if (!_navigationInterceptionEnabled)
{
_navigationInterceptionEnabled = true;
await NavigationInterception.EnableNavigationInterceptionAsync();
Refresh();
}
}
public void Dispose()
{
NavigationManager.LocationChanged -= HandleLocationChanged;
}
private void HandleLocationChanged(object sender, LocationChangedEventArgs args)
{
_location = args.Location;
Refresh();
}
private void Refresh()
{
var relativeUri = NavigationManager.ToBaseRelativePath(_location);
var hash = string.Empty;
if (relativeUri.IndexOf('#') >= 0)
{
hash = relativeUri.Substring(relativeUri.IndexOf('#'), relativeUri.Length - relativeUri.IndexOf('#'));
relativeUri = relativeUri.Substring(0, relativeUri.IndexOf('#'));
}
var currentCulture = LanguageService.CurrentCulture;
var segment = relativeUri.IndexOf('/') > 0 ? relativeUri.Substring(0, relativeUri.IndexOf('/')) : relativeUri;
if (string.IsNullOrWhiteSpace(segment))
{
NavigationManager.NavigateTo($"{currentCulture.Name}/{relativeUri}");
return;
}
else
{
if (segment.IsIn("zh-CN", "en-US"))
{
LanguageService.SetLanguage(CultureInfo.GetCultureInfo(segment));
}
else if (currentCulture.Name.IsIn("zh-CN", "en-US"))
{
NavigationManager.NavigateTo($"{currentCulture.Name}/{relativeUri}");
}
else
{
NavigationManager.NavigateTo($"en-US/{relativeUri}");
return;
}
}
var matchResult = RouteManager.Match(relativeUri);
if (matchResult.IsMatch)
{
var routeData = new RouteData(matchResult.MatchedRoute.PageType, matchResult.MatchedRoute.Parameters);
_renderHandle.Render(Found(routeData));
}
else
{
if (!string.IsNullOrEmpty(DefaultUrl))
{
NavigationManager.NavigateTo($"{currentCulture}/{DefaultUrl}");
}
_renderHandle.Render(NotFound);
}
if (!string.IsNullOrWhiteSpace(hash))
{
if (JsRuntime.IsBrowser())
{
((IJSInProcessRuntime)JsRuntime).InvokeVoid(JSInteropConstants.ScrollTo, $"{hash}");
}
else
{
Task.Run(() => JsRuntime.InvokeVoidAsync(JSInteropConstants.ScrollTo, $"{hash}"));
}
}
}
}
}

View File

@ -1,24 +0,0 @@
namespace AntDesign.Docs.Routing
{
public class MatchResult
{
public bool IsMatch { get; set; }
public Route MatchedRoute { get; set; }
public MatchResult(bool isMatch, Route matchedRoute)
{
IsMatch = isMatch;
MatchedRoute = matchedRoute;
}
public static MatchResult Match(Route matchedRoute)
{
return new MatchResult(true, matchedRoute);
}
public static MatchResult NoMatch()
{
return new MatchResult(false, null);
}
}
}

View File

@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
namespace AntDesign.Docs.Routing
{
public class Route
{
public Type PageType { get; set; }
internal RouteTemplate Template { get; set; }
public string[] UnusedRouteParameterNames { get; set; }
public Dictionary<string, object> Parameters { get; set; }
public MatchResult Match(string[] segments, string relativeUri)
{
Dictionary<string, object> parameters = []; //ParseQueryString(relativeUri);
if (Template.Segments.Length != segments.Length)
{
return MatchResult.NoMatch();
}
for (var i = 0; i < Template.Segments.Length; i++)
{
var segment = Template.Segments[i];
var pathSegment = segments[i];
if (!segment.Match(pathSegment, out var matchedParameterValue))
{
return MatchResult.NoMatch();
}
else
{
if (segment.IsParameter)
{
parameters[segment.Value] = matchedParameterValue;
}
}
}
// In addition to extracting parameter values from the URL, each route entry
// also knows which other parameters should be supplied with null values. These
// are parameters supplied by other route entries matching the same handler.
if (UnusedRouteParameterNames.Length > 0)
{
foreach (var name in UnusedRouteParameterNames)
{
parameters[name] = null;
}
}
this.Parameters = parameters;
return MatchResult.Match(this);
}
private Dictionary<string, object> ParseQueryString(string uri)
{
// Parameters will be lazily initialized.
Dictionary<string, object> querystring = null;
foreach (string kvp in uri.Substring(uri.IndexOf("?", StringComparison.Ordinal) + 1).Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries))
{
if (kvp != "" && kvp.Contains("="))
{
var pair = kvp.Split('=');
querystring ??= new Dictionary<string, object>(StringComparer.Ordinal);
querystring.Add(pair[0], pair[1]);
}
}
return querystring;
}
}
}

View File

@ -1,112 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Globalization;
namespace AntDesign.Docs.Routing
{
internal abstract class RouteConstraint
{
// note: the things that prevent this cache from growing unbounded is that
// we're the only caller to this code path, and the fact that there are only
// 8 possible instances that we create.
//
// The values passed in here for parsing are always static text defined in route attributes.
private static readonly ConcurrentDictionary<string, RouteConstraint> _cachedConstraints
= new ConcurrentDictionary<string, RouteConstraint>();
public abstract bool Match(string pathSegment, out object convertedValue);
public static RouteConstraint Parse(string template, string segment, string constraint)
{
if (string.IsNullOrEmpty(constraint))
{
throw new ArgumentException($"Malformed segment '{segment}' in route '{template}' contains an empty constraint.");
}
if (_cachedConstraints.TryGetValue(constraint, out var cachedInstance))
{
return cachedInstance;
}
else
{
var newInstance = CreateRouteConstraint(constraint);
if (newInstance != null)
{
// We've done to the work to create the constraint now, but it's possible
// we're competing with another thread. GetOrAdd can ensure only a single
// instance is returned so that any extra ones can be GC'ed.
return _cachedConstraints.GetOrAdd(constraint, newInstance);
}
else
{
throw new ArgumentException($"Unsupported constraint '{constraint}' in route '{template}'.");
}
}
}
private static RouteConstraint CreateRouteConstraint(string constraint)
{
switch (constraint)
{
case "bool":
return new TypeRouteConstraint<bool>(bool.TryParse);
case "datetime":
return new TypeRouteConstraint<DateTime>((string str, out DateTime result)
=> DateTime.TryParse(str, CultureInfo.InvariantCulture, DateTimeStyles.None, out result));
case "decimal":
return new TypeRouteConstraint<decimal>((string str, out decimal result)
=> decimal.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
case "double":
return new TypeRouteConstraint<double>((string str, out double result)
=> double.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
case "float":
return new TypeRouteConstraint<float>((string str, out float result)
=> float.TryParse(str, NumberStyles.Number, CultureInfo.InvariantCulture, out result));
case "guid":
return new TypeRouteConstraint<Guid>(Guid.TryParse);
case "int":
return new TypeRouteConstraint<int>((string str, out int result)
=> int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
case "long":
return new TypeRouteConstraint<long>((string str, out long result)
=> long.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result));
default:
return null;
}
}
}
internal class TypeRouteConstraint<T> : RouteConstraint
{
public delegate bool TryParseDelegate(string str, out T result);
private readonly TryParseDelegate _parser;
public TypeRouteConstraint(TryParseDelegate parser)
{
_parser = parser;
}
public override bool Match(string pathSegment, out object convertedValue)
{
if (_parser(pathSegment, out var result))
{
convertedValue = result;
return true;
}
else
{
convertedValue = null;
return false;
}
}
}
}

View File

@ -1,117 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using AntDesign.Docs.Localization;
using Microsoft.AspNetCore.Components;
namespace AntDesign.Docs.Routing
{
public class RouteManager
{
public Route[] Routes { get; private set; }
private readonly ILanguageService _languageService;
public RouteManager(ILanguageService languageService)
{
_languageService = languageService;
}
public void Initialise(Assembly appAssembly)
{
var pageComponentTypes = appAssembly
.ExportedTypes
.Where(t => t.Namespace != null && (t.IsSubclassOf(typeof(ComponentBase))
&& t.Namespace.Contains(".Pages")));
var routesList = new List<Route>();
foreach (var pageType in pageComponentTypes)
{
if (pageType.FullName == null)
continue;
var uriSegments = pageType.FullName.Substring(pageType.FullName.IndexOf("Pages", StringComparison.Ordinal) + 6).Split('.');
var routeAttributes = pageType.GetCustomAttributes<RouteAttribute>(inherit: false);
if (!routeAttributes.Any())
{
routeAttributes = new[] { new RouteAttribute($"/{string.Join("/", uriSegments)}"), };
}
var templates = routeAttributes.Select(t => t.Template).ToArray();
var parsedTemplates = templates.Select(TemplateParser.ParseTemplate).ToArray();
var allRouteParameterNames = parsedTemplates
.SelectMany(GetParameterNames)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
foreach (var parsedTemplate in parsedTemplates)
{
var unusedRouteParameterNames = allRouteParameterNames
.Except(GetParameterNames(parsedTemplate), StringComparer.OrdinalIgnoreCase)
.ToArray();
var newRoute = new Route
{
PageType = pageType,
Template = parsedTemplate,
UnusedRouteParameterNames = unusedRouteParameterNames
};
routesList.Add(newRoute);
}
}
Routes = routesList.ToArray();
}
public MatchResult Match(string relativeUri)
{
var originalUri = relativeUri;
if (relativeUri.IndexOf('?') > -1)
{
relativeUri = relativeUri.Substring(0, relativeUri.IndexOf('?'));
}
var segments = relativeUri.Trim().Split('/', StringSplitOptions.RemoveEmptyEntries).Select(Uri.UnescapeDataString).ToArray();
if (segments.Length == 0)
{
var indexRoute = Routes.SingleOrDefault(x => x.PageType.FullName != null && x.PageType.FullName.ToLower().EndsWith("index"));
if (indexRoute != null)
{
return MatchResult.Match(indexRoute);
}
}
if (segments[0] == _languageService.CurrentCulture.Name)
{
segments = segments[1..];
}
foreach (var route in Routes)
{
var matchResult = route.Match(segments, originalUri);
if (matchResult.IsMatch)
{
return matchResult;
}
}
return MatchResult.NoMatch();
}
private static string[] GetParameterNames(RouteTemplate routeTemplate)
{
return routeTemplate.Segments
.Where(s => s.IsParameter)
.Select(s => s.Value)
.ToArray();
}
}
}

View File

@ -1,18 +0,0 @@
using System.Diagnostics;
namespace AntDesign.Docs.Routing
{
[DebuggerDisplay("{TemplateText}")]
internal class RouteTemplate
{
public RouteTemplate(string templateText, TemplateSegment[] segments)
{
TemplateText = templateText;
Segments = segments;
}
public string TemplateText { get; }
public TemplateSegment[] Segments { get; }
}
}

View File

@ -1,92 +0,0 @@
using System;
namespace AntDesign.Docs.Routing
{
internal class TemplateParser
{
public static readonly char[] InvalidParameterNameCharacters =
new char[] { '*', '?', '{', '}', '=', '.' };
internal static RouteTemplate ParseTemplate(string template)
{
var originalTemplate = template;
template = template.Trim('/');
if (template == "")
{
// Special case "/";
return new RouteTemplate("/", Array.Empty<TemplateSegment>());
}
var segments = template.Split('/');
var templateSegments = new TemplateSegment[segments.Length];
for (int i = 0; i < segments.Length; i++)
{
var segment = segments[i];
if (string.IsNullOrEmpty(segment))
{
throw new InvalidOperationException(
$"Invalid template '{template}'. Empty segments are not allowed.");
}
if (segment[0] != '{')
{
if (segment[^1] == '}')
{
throw new InvalidOperationException(
$"Invalid template '{template}'. Missing '{{' in parameter segment '{segment}'.");
}
templateSegments[i] = new TemplateSegment(originalTemplate, segment, isParameter: false);
}
else
{
if (segment[^1] != '}')
{
throw new InvalidOperationException(
$"Invalid template '{template}'. Missing '}}' in parameter segment '{segment}'.");
}
if (segment.Length < 3)
{
throw new InvalidOperationException(
$"Invalid template '{template}'. Empty parameter name in segment '{segment}' is not allowed.");
}
var invalidCharacter = segment.IndexOfAny(InvalidParameterNameCharacters, 1, segment.Length - 2);
if (invalidCharacter != -1)
{
throw new InvalidOperationException(
$"Invalid template '{template}'. The character '{segment[invalidCharacter]}' in parameter segment '{segment}' is not allowed.");
}
templateSegments[i] = new TemplateSegment(originalTemplate, segment.Substring(1, segment.Length - 2), isParameter: true);
}
}
for (int i = 0; i < templateSegments.Length; i++)
{
var currentSegment = templateSegments[i];
if (!currentSegment.IsParameter)
{
continue;
}
for (int j = i + 1; j < templateSegments.Length; j++)
{
var nextSegment = templateSegments[j];
if (!nextSegment.IsParameter)
{
continue;
}
if (string.Equals(currentSegment.Value, nextSegment.Value, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"Invalid template '{template}'. The parameter '{currentSegment}' appears multiple times.");
}
}
}
return new RouteTemplate(template, templateSegments);
}
}
}

View File

@ -1,63 +0,0 @@
using System;
using System.Linq;
namespace AntDesign.Docs.Routing
{
internal class TemplateSegment
{
public TemplateSegment(string template, string segment, bool isParameter)
{
IsParameter = isParameter;
if (!isParameter || segment.IndexOf(':') < 0)
{
Value = segment;
Constraints = Array.Empty<RouteConstraint>();
}
else
{
var tokens = segment.Split(':');
if (tokens[0].Length == 0)
{
throw new ArgumentException($"Malformed parameter '{segment}' in route '{template}' has no name before the constraints list.");
}
Value = tokens[0];
Constraints = tokens.Skip(1)
.Select(token => RouteConstraint.Parse(template, segment, token))
.ToArray();
}
}
// The value of the segment. The exact text to match when is a literal.
// The parameter name when its a segment
public string Value { get; }
public bool IsParameter { get; }
public RouteConstraint[] Constraints { get; }
public bool Match(string pathSegment, out object matchedParameterValue)
{
if (IsParameter)
{
matchedParameterValue = pathSegment;
foreach (var constraint in Constraints)
{
if (!constraint.Match(pathSegment, out matchedParameterValue))
{
return false;
}
}
return true;
}
else
{
matchedParameterValue = null;
return string.Equals(Value, pathSegment, StringComparison.OrdinalIgnoreCase);
}
}
}
}

View File

@ -1,7 +1,6 @@
using System.Reflection;
using AntDesign.Docs.Highlight;
using AntDesign.Docs.Localization;
using AntDesign.Docs.Routing;
using AntDesign.Docs.Services;
namespace Microsoft.Extensions.DependencyInjection
@ -11,7 +10,6 @@ namespace Microsoft.Extensions.DependencyInjection
public static IServiceCollection AddAntDesignDocs(this IServiceCollection services)
{
services.AddAntDesign();
services.AddSingleton<RouteManager>();
services.AddScoped<DemoService>();
services.AddScoped<IconListService>();
services.AddSingleton<ILanguageService>(new InAssemblyLanguageService(Assembly.GetExecutingAssembly()));

View File

@ -27,6 +27,8 @@ namespace AntDesign.Docs.Shared
private DemoMenuItem[] _menuItems = { };
private bool _firstRender;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
@ -46,12 +48,17 @@ namespace AntDesign.Docs.Shared
{
if (firstRender)
{
_firstRender = true;
await JsInterop.InvokeVoidAsync("window.DocSearch.init", CurrentLanguage);
}
}
private async void OnLanguageChanged(object sender, CultureInfo culture)
{
if (!_firstRender)
{
return;
}
_menuItems = await DemoService.GetMenuAsync();
await JsInterop.InvokeVoidAsync("window.DocSearch.init", culture.Name);
await InvokeAsync(StateHasChanged);

View File

@ -6,7 +6,6 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using AntDesign
@using AntDesign.Docs.Routing
@using AntDesign.Docs.Pages
@using AntDesign.Docs.Shared
@using AntDesign.Docs.Localization