Skip to content

Commit bf30ff0

Browse files
committed
Add Nav Menu
1 parent 80e3c9e commit bf30ff0

File tree

7 files changed

+164
-15
lines changed

7 files changed

+164
-15
lines changed

BlazorPreRender/BlazorApp1/BlazorApp1/Pages/Counter.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/counter"
2+
@attribute [System.ComponentModel.Description("Counter")]
23

34
<h1>Counter</h1>
45

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@page "/Example"
2+
@attribute [System.ComponentModel.Description("Example")]
3+
4+
<h3>Example</h3>

BlazorPreRender/BlazorApp1/BlazorApp1/Pages/Index.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@page "/"
2+
@attribute [System.ComponentModel.Description("Home")]
23

34
<h1>Hello, world!</h1>
45

BlazorPreRender/BlazorApp1/BlazorApp1/Shared/NavMenu.razor

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,14 @@
77

88
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
99
<ul class="nav flex-column">
10-
<li class="nav-item px-3">
11-
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
12-
<span class="oi oi-home" aria-hidden="true"></span> Home
13-
</NavLink>
14-
</li>
15-
<li class="nav-item px-3">
16-
<NavLink class="nav-link" href="counter">
17-
<span class="oi oi-plus" aria-hidden="true"></span> Counter
18-
</NavLink>
19-
</li>
20-
<li class="nav-item px-3">
21-
<NavLink class="nav-link" href="fetchdata">
22-
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
23-
</NavLink>
24-
</li>
10+
@foreach (var pageDetail in PageDetails.MenuPages)
11+
{
12+
<li class="nav-item px-3">
13+
<NavLink class="nav-link" href="@pageDetail.Route" Match="NavLinkMatch.All">
14+
@pageDetail.Title
15+
</NavLink>
16+
</li>
17+
}
2518
</ul>
2619
</div>
2720

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Diagnostics;
5+
using System.Linq;
6+
using System.Text;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Text;
11+
12+
namespace SourceGenerators
13+
{
14+
[Generator]
15+
public class PageDetailsGenerator : ISourceGenerator
16+
{
17+
private const string RouteAttributeName = "Microsoft.AspNetCore.Components.RouteAttribute";
18+
private const string DescriptionAttributeName = "System.ComponentModel.Description";
19+
20+
public void Execute(GeneratorExecutionContext context)
21+
{
22+
try
23+
{
24+
// Debugger.Launch();
25+
26+
IEnumerable<RouteableComponent> menuComponents = GetMenuComponents(context.Compilation);
27+
28+
context.AddSource("PageDetail", SourceText.From(Templates.PageDetail(), Encoding.UTF8));
29+
var pageDetailsSource = SourceText.From(Templates.MenuPages(menuComponents), Encoding.UTF8);
30+
context.AddSource("PageDetails", pageDetailsSource);
31+
}
32+
catch (Exception)
33+
{
34+
Debugger.Launch();
35+
}
36+
}
37+
38+
public void Initialize(GeneratorInitializationContext context)
39+
{
40+
//Debugger.Launch();
41+
}
42+
43+
private static ImmutableArray<RouteableComponent> GetMenuComponents(Compilation compilation)
44+
{
45+
// Get all classes
46+
IEnumerable<SyntaxNode> allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
47+
IEnumerable<ClassDeclarationSyntax> allClasses = allNodes
48+
.Where(d => d.IsKind(SyntaxKind.ClassDeclaration))
49+
.OfType<ClassDeclarationSyntax>();
50+
51+
return allClasses
52+
.Select(component => TryGetMenuComponent(compilation, component))
53+
.Where(page => page is not null)
54+
.Cast<RouteableComponent>()// stops the nullable lies
55+
.ToImmutableArray();
56+
}
57+
58+
private static RouteableComponent? TryGetMenuComponent(Compilation compilation, ClassDeclarationSyntax component)
59+
{
60+
var attributes = component.AttributeLists
61+
.SelectMany(x => x.Attributes)
62+
.Where(attr =>
63+
attr.Name.ToString() == RouteAttributeName
64+
|| attr.Name.ToString() == DescriptionAttributeName)
65+
.ToList();
66+
67+
var routeAttribute = attributes.FirstOrDefault(attr => attr.Name.ToString() == RouteAttributeName);
68+
var descriptionAttribute = attributes.FirstOrDefault(attr => attr.Name.ToString() == DescriptionAttributeName);
69+
70+
if (routeAttribute is null || descriptionAttribute is null)
71+
{
72+
return null;
73+
}
74+
75+
if (
76+
routeAttribute.ArgumentList?.Arguments.Count != 1 ||
77+
descriptionAttribute.ArgumentList?.Arguments.Count != 1)
78+
{
79+
// no route path or description value
80+
return null;
81+
}
82+
83+
var semanticModel = compilation.GetSemanticModel(component.SyntaxTree);
84+
85+
var routeArg = routeAttribute.ArgumentList.Arguments[0];
86+
var routeExpr = routeArg.Expression;
87+
var routeTemplate = semanticModel.GetConstantValue(routeExpr).ToString();
88+
89+
var descriptionArg = descriptionAttribute.ArgumentList.Arguments[0];
90+
var descriptionExpr = descriptionArg.Expression;
91+
var title = semanticModel.GetConstantValue(descriptionExpr).ToString();
92+
93+
return new RouteableComponent(routeTemplate, title);
94+
}
95+
}
96+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace SourceGenerators
2+
{
3+
public class RouteableComponent
4+
{
5+
public string Route { get; }
6+
public string Title { get; }
7+
8+
public RouteableComponent(string route, string title)
9+
{
10+
Title = title;
11+
Route = route;
12+
}
13+
}
14+
}

BlazorPreRender/BlazorApp1/SourceGenerators/Templates.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,46 @@ public static class AppRoutes
2525
{
2626
sb.AppendLine($"\"{route}\",");
2727
}
28+
29+
sb.Append(@"
30+
}
31+
);
32+
}
33+
}");
34+
return sb.ToString();
35+
}
36+
37+
public static string PageDetail()
38+
{
39+
return @"
40+
namespace BlazorApp1
41+
{
42+
public record PageDetail(string Route, string Title);
43+
}
44+
";
45+
}
46+
47+
public static string MenuPages(IEnumerable<RouteableComponent> pages)
48+
{
49+
// hard code the namespace for now
50+
var sb = new StringBuilder(@"
51+
using System.Collections.Generic;
52+
using System.Collections.ObjectModel;
53+
54+
namespace BlazorApp1
55+
{
56+
public static class PageDetails
57+
{
58+
public static ReadOnlyCollection<PageDetail> MenuPages { get; } =
59+
new ReadOnlyCollection<PageDetail>(
60+
new List<PageDetail>
61+
{
62+
");
63+
foreach (var page in pages)
64+
{
65+
sb.AppendLine($"new PageDetail(\"{page.Route}\", \"{page.Title}\"),");
66+
}
67+
2868
sb.Append(@"
2969
}
3070
);

0 commit comments

Comments
 (0)